online compiler and debugger for c/c++

code. compile. run. debug. share.
Source Code    Language
#include <iostream> #include <fstream> #include <string> #include <sstream> #include <chrono> #include <ctime> #include <cassert> #include <vector> #include <memory> #include <map> #include <chrono> #include <algorithm> #include <deque> #include <cctype> #include <thread> #include <array> #include <cstdint> typedef std::deque<std::string> stringlist; std::ostream& operator<<(std::ostream & os, const stringlist & list) { os << '('; for (auto s : list) os << '"' << s << '"' << ' '; os << ')'; return os; } namespace micro_test_library { unsigned test_count; // total number of tests executed unsigned fault_count; // total number of tests that fail template<typename A, typename B> void test_equal(const A & value, const B & expected_value, const char * filename, const size_t linenum, const char * functionname) { ++test_count; if (!(value == expected_value)) { ++fault_count; // e.g. love.cpp(2021) : in proposal() expected 'Yes!', but got 'Hahaha' std::cout << filename << '(' << linenum << ") : in " << functionname << "() expected '" << expected_value << "', but got '" << value << "'\n"; } } #define TEST_EQUAL(value, expected_value) \ { \ micro_test_library::test_equal(value, expected_value, \ __FILE__, __LINE__, __FUNCTION__); \ } // list of all test routines to be executed std::vector<void (*)()> test_routines; size_t add_test(void (*f)()) { test_routines.push_back(f); return test_routines.size(); } #define DEF_TEST_FUNC(test_func) \ void test_func(); \ size_t micro_test_##test_func = micro_test_library::add_test(test_func); \ void test_func() void run_tests() { for (auto & t : test_routines) t(); } #define RUN_TESTS() micro_test_library::run_tests() } //namespace micro_test_library // remove front element of given container and return it template<typename T> auto pop_front(T & container) { auto v(container.front()); container.pop_front(); return v; } template<> auto pop_front(std::string & container) { auto v(container.front()); container.erase(0, 1); return v; } // join given words into one space separated string // e.g. join(["one", "two", ",", "3", "."]) -> "one two , 3 ." // (ELIZA doesn't output punctuation) std::string join(const stringlist & words) { std::string result; for (const auto & word : words) { if (!word.empty()) { if (!result.empty()) result += ' '; result += word; } } return result; } DEF_TEST_FUNC(join_test) { TEST_EQUAL(join({ }), ""); TEST_EQUAL(join({ "ELIZA" }), "ELIZA"); TEST_EQUAL(join({ "one", "", "two", ",", "3", "." }), "one two , 3 ."); } namespace elizalogic { // the core ELIZA algorithm // map tag -> associated words, e.g. "BELIEF" -> ("BELIEVE" "FEEL" "THINK" "WISH") typedef std::map<std::string, stringlist> tagmap; constexpr unsigned char hollerith_undefined = 0xFFu; // (must be > 63) const std::array<unsigned char, 256> hollerith_encoding{ []{ static constexpr unsigned char bcd[64] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 0, '=', '\'', 0, 0, 0, '+', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 0, '.', ')', 0, 0, 0, '-', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 0, '$', '*', 0, 0, 0, ' ', '/', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0, ',', '(', 0, 0, 0 }; static_assert(std::numeric_limits<unsigned char>::max() == 255); std::array<unsigned char, 256> to_bcd; to_bcd.fill(hollerith_undefined); for (unsigned char c = 0; c < 64; ++c) if (bcd[c]) to_bcd[bcd[c]] = c; return to_bcd; }() }; // return true iff given c is in the Hollerith character set bool hollerith_defined(char c) { static_assert(std::numeric_limits<unsigned char>::min() == 0); static_assert(std::numeric_limits<unsigned char>::max() == 255); return hollerith_encoding[static_cast<unsigned char>(c)] != hollerith_undefined; } // return given string s with non-Hollerith characters replaced // by space, and '?' and '!' replaced by '.' std::string filter_bcd(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](char c) { if (c == '?' || c == '!') return '.'; return hollerith_defined(c) ? c : ' '; } ); return s; } DEF_TEST_FUNC(filter_bcd_test) { TEST_EQUAL(filter_bcd(""), ""); TEST_EQUAL(filter_bcd("HELLO"), "HELLO"); TEST_EQUAL(filter_bcd("Hello! How are you?"), "H . H ."); const std::string all_valid_bcd{ "0123456789=\'+ABCDEFGHI.)-JKLMNOPQR$* /STUVWXYZ,(" }; TEST_EQUAL(filter_bcd(all_valid_bcd), all_valid_bcd); } bool punctuation(char c) { return c == ',' || c == '.'; // [page 37 (c)] } bool delimiter(const std::string & s) { return s == "BUT" || (s.size() == 1 && punctuation(s[0])); } stringlist split(std::string s) { stringlist result; std::string word; for (auto ch : s) { if (punctuation(ch) || ch == ' ') { if (!word.empty()) { result.push_back(word); word.clear(); } if (ch != ' ') result.push_back(std::string(1, ch)); } else word.push_back(ch); } if (!word.empty()) result.push_back(word); return result; } DEF_TEST_FUNC(split_test) { const stringlist r1{ "one", "two", ",", "three", "." }; TEST_EQUAL(split("one two, three."), r1); } // return given string s in uppercase std::string to_upper(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return static_cast<char>(std::toupper(c)); } ); return s; } // return numeric value of given s or -1, e.g. to_int("2") -> 2, to_int("two") -> -1 int to_int(const std::string & s) { int result = 0; for (auto c : s) { if (std::isdigit(c)) result = 10 * result + c - '0'; else return -1; } return result; } bool inlist(const std::string & word, std::string wordlist, const tagmap & tags) { if (wordlist.back() == ')') wordlist.pop_back(); const char * cp = wordlist.data(); if (*cp == '(') ++cp; stringlist s; if (*cp == '*') { ++cp; while (*cp == ' ') ++cp; s = split(std::string(cp)); } else if (*cp == '/') { ++cp; while (*cp == ' ') ++cp; const auto t = tags.find(std::string(cp)); if (t != tags.end()) s = t->second; } return std::find(s.begin(), s.end(), word) != s.end(); } bool match(const tagmap & tags, stringlist pattern, stringlist words, stringlist & matching_components) { matching_components.clear(); if (pattern.empty()) return words.empty(); auto patword = pop_front(pattern); int n = to_int(patword); if (n < 0) { // patword is e.g. "ARE" or "(*SAD HAPPY DEPRESSED)" if (words.empty()) return false; // patword cannot match nothing std::string current_word = pop_front(words); if (patword.front() == '(') { // patword is a group, is current_word in that group? if (!inlist(current_word, patword, tags)) return false; } else if (patword != current_word) return false; // patword is a single word and it doesn't match // so far so good; can we match remainder of pattern with remainder of words? stringlist mc; if (match(tags, pattern, words, mc)) { matching_components.push_back(current_word); matching_components.insert(matching_components.end(), mc.begin(), mc.end()); return true; } } else if (n == 0) { // 0 matches zero or more of any words stringlist component; stringlist mc; for (;;) { if (match(tags, pattern, words, mc)) { matching_components.push_back(join(component)); matching_components.insert(matching_components.end(), mc.begin(), mc.end()); return true; } if (words.empty()) return false; component.push_back(pop_front(words)); } } else { // match exactly n of any words [page 38 (a)] if (words.size() < static_cast<size_t>(n)) return false; stringlist component; for (int i = 0; i < n; ++i) component.push_back(pop_front(words)); stringlist mc; if (match(tags, pattern, words, mc)) { matching_components.push_back(join(component)); matching_components.insert(matching_components.end(), mc.begin(), mc.end()); return true; } } return false; } DEF_TEST_FUNC(match_test) { // test [0, YOU, (*WANT NEED), 0] matches [YOU, NEED, NICE, FOOD] stringlist words{ "YOU", "NEED", "NICE", "FOOD" }; stringlist pattern{ "0", "YOU", "(*WANT NEED)", "0" }; stringlist expected{ "", "YOU", "NEED", "NICE FOOD" }; stringlist matching_components; TEST_EQUAL(match({}, pattern, words, matching_components), true); TEST_EQUAL(matching_components, expected); // test [0, 0, YOU, (*WANT NEED), 0] matches [YOU, WANT, NICE, FOOD] words = { "YOU", "WANT", "NICE", "FOOD" }; pattern = { "0", "0", "YOU", "(*WANT NEED)", "0" }; expected = {"", "", "YOU", "WANT", "NICE FOOD" }; TEST_EQUAL(match({}, pattern, words, matching_components), true); TEST_EQUAL(matching_components, expected); // test [1, (*WANT NEED), 0] matches [YOU, WANT, NICE, FOOD] words = { "YOU", "WANT", "NICE", "FOOD" }; pattern = { "1", "(*WANT NEED)", "0" }; expected = { "YOU", "WANT", "NICE FOOD" }; TEST_EQUAL(match({}, pattern, words, matching_components), true); TEST_EQUAL(matching_components, expected); // test [1, (*WANT NEED), 1] doesn't match [YOU, WANT, NICE, FOOD] words = { "YOU", "WANT", "NICE", "FOOD" }; pattern = { "1", "(*WANT NEED)", "1" }; TEST_EQUAL(match({}, pattern, words, matching_components), false); // test [1, (*WANT NEED), 2] matches [YOU, WANT, NICE, FOOD] words = { "YOU", "WANT", "NICE", "FOOD" }; pattern = { "1", "(*WANT NEED)", "2" }; expected = { "YOU", "WANT", "NICE FOOD" }; TEST_EQUAL(match({}, pattern, words, matching_components), true); TEST_EQUAL(matching_components, expected); // A test pattern from the YMATCH function description in the SLIP manual words = { "MARY", "HAD", "A", "LITTLE", "LAMB", "ITS", "PROBABILITY", "WAS", "ZERO" }; pattern = { "MARY", "2", "2", "ITS", "1", "0" }; expected = { "MARY", "HAD A", "LITTLE LAMB", "ITS", "PROBABILITY", "WAS ZERO" }; TEST_EQUAL(match({}, pattern, words, matching_components), true); TEST_EQUAL(matching_components, expected); } // return words constructed from given reassembly_rule and components // e.g. reassemble([ARE, YOU, 1], [MAD, ABOUT YOU]) -> [ARE, YOU, MAD] stringlist reassemble(const stringlist & reassembly_rule, const stringlist & components) { stringlist result; for (const auto & r : reassembly_rule) { int n = to_int(r); if (n < 0) result.push_back(r); else if (n == 0 || static_cast<size_t>(n) > components.size()) result.push_back("SCRIPT-ERROR-REASSEMBLY-RULE-INDEX-OUT-OF-RANGE"); else { stringlist expanded = split(components[n - 1]); result.insert(result.end(), expanded.begin(), expanded.end()); } } return result; } DEF_TEST_FUNC(reassemble_test) { // A test pattern from the ASSMBL function description in the SLIP manual // (using above matching_components list) stringlist matching_components { "MARY", "HAD A", "LITTLE LAMB", "ITS", "PROBABILITY", "WAS ZERO" }; stringlist reassembly_rule{ "DID", "1", "HAVE", "A", "3" }; stringlist expected { "DID", "MARY", "HAVE", "A", "LITTLE", "LAMB" }; TEST_EQUAL(reassemble(reassembly_rule, matching_components), expected); } int hash(uint_least64_t d, int n) { assert(0 <= n && n <= 15); d &= 0x7FFFFFFFFull; // clear the "sign" bit d *= d; // square it d >>= 35 - n / 2; // move middle n bits to least sig. bits // return d & (1ull << n) - 1; // mask off all but n least sig. bits return d & ((1ull << n) - 1); // mask off all but n least sig. bits - think this ok // return (d & (1ull << n)) - 1; // mask off all but n least sig. bits } DEF_TEST_FUNC(hash_test) { TEST_EQUAL(hash(0214366217062ull, 7), 14l); TEST_EQUAL(hash(0302551256060ull, 2), 3l); TEST_EQUAL(hash(0423124626060ull, 2), 1l); TEST_EQUAL(hash(0633144256060ull, 2), 0l); TEST_EQUAL(hash(0777777777777ull, 7), 0x70l); TEST_EQUAL(hash(0777777777777ull, 15), 0x7F00l); TEST_EQUAL(hash(0x555555555ull, 15), 0x46E3l); TEST_EQUAL(hash(0xF0F0F0F0Full, 15), 0x7788l); TEST_EQUAL(hash(0214366217062ull, 15), 0x70EEl); TEST_EQUAL(hash(0633144256060ull, 15), 0x252El); TEST_EQUAL(hash(0ull, 7), 0); } uint_least64_t last_chunk_as_bcd(std::string s) { uint_least64_t result = 0; auto append = [&](char c) { assert(hollerith_defined(c)); result <<= 6; result |= hollerith_encoding[static_cast<unsigned char>(c)]; }; int count = 0; if (!s.empty()) { for (auto c = std::next(s.begin(), ((s.length() - 1) / 6) * 6); c != std::end(s); ++c, ++count) { append(*c); } } while (count++ < 6) append(' '); return result; } DEF_TEST_FUNC(last_chunk_as_bcd_test) { // _ _ _ _ _ _ TEST_EQUAL(last_chunk_as_bcd(""), 0606060606060ull); // _ _ _ _ _ _ TEST_EQUAL(last_chunk_as_bcd("X"), 0676060606060ull); // H E R E _ _ TEST_EQUAL(last_chunk_as_bcd("HERE"), 0302551256060ull); // A L W A Y S TEST_EQUAL(last_chunk_as_bcd("ALWAYS"), 0214366217062ull); // E D _ _ _ _ TEST_EQUAL(last_chunk_as_bcd("INVENTED"), 0252460606060ull); // A B C D E F TEST_EQUAL(last_chunk_as_bcd("123456ABCDEF"), 0212223242526ull); } // interface and data shared by all rules class rule_base { public: rule_base() {} rule_base(const std::string & keyword, const std::string & word_substitution, int precedence) : keyword_(keyword), word_substitution_(word_substitution), precedence_(precedence) {} virtual ~rule_base() {} void add_transformation_rule(const stringlist & decomposition, const std::vector<stringlist> & reassembly_rules) { trans_.push_back(transform(decomposition, reassembly_rules)); } int precedence() const { return precedence_; } std::string keyword() const { return keyword_; } enum action { inapplicable, // no transformation could be performed complete, // transformation of input is complete newkey, // request caller try next keyword in keystack linkkey // request caller try returned keyword }; // replace 'word' with substitute specified by script rule, if any virtual action apply_word_substitution(std::string & word) { if (word_substitution_.empty() || word != keyword_) return inapplicable; word = word_substitution_; return complete; } // return true iff this rule has whole-sentence transformation rules associated with it virtual bool has_transformation() const { return false; } // use this rule's decomposition/reassembly rules to transform given 'words' virtual action apply_transformation(stringlist & /*words*/, const tagmap & /*tags*/, std::string & /*link_keyword*/) { return inapplicable; } virtual stringlist dlist_tags() const { return stringlist(); } virtual std::string to_string() const = 0; protected: std::string keyword_; // the word that triggers this rule std::string word_substitution_; // the word that is to replace the keyword, if any int precedence_{ 0 }; // the highest priority rule is selected first struct transform { // decomposition and associated reassembly rules stringlist decomposition; std::vector<stringlist> reassembly_rules; unsigned next_reassembly_rule{ 0 }; transform() {} transform(const stringlist & decomposition, const std::vector<stringlist> & reassembly_rules) : decomposition(decomposition), reassembly_rules(reassembly_rules) {} }; std::vector<transform> trans_; // transformations associated with this rule }; std::string rule_base::to_string() const { return std::string(); } // map keyword -> transformation rule typedef std::map<std::string, std::shared_ptr<rule_base>> rulemap; // in the rules map, the special-cases NONE and MEMORY have keys // that cannot match any user input text #define SPECIAL_RULE_NONE "zNONE" #define SPECIAL_RULE_MEMORY "zMEMORY" // e.g. (ME = YOU) class rule_unconditional_substitution : public rule_base { public: rule_unconditional_substitution() {} rule_unconditional_substitution(const std::string & keyword, const std::string & word_substitution) : rule_base(keyword, word_substitution, 0) {} virtual std::string to_string() const { return "(" + keyword_ + " = " + word_substitution_ + ")\n"; } }; // e.g. (MOM = MOTHER DLIST(/ FAMILY)) class rule_dlist : public rule_base { public: rule_dlist() {} rule_dlist(const std::string & keyword, const std::string & word_substitution, const stringlist & tags) : rule_base(keyword, word_substitution, 0), tags_(tags) { } stringlist dlist_tags() const { return tags_; } virtual std::string to_string() const { std::string sexp("("); sexp += keyword_; if (!word_substitution_.empty()) sexp += " = " + word_substitution_; sexp += " DLIST(" + join(tags_) + "))\n"; return sexp; } private: stringlist tags_; }; // e.g. (DREAMED = DREAMT 4 (=DREAMT)) class rule_equivalence_class : public rule_base { public: rule_equivalence_class() {} rule_equivalence_class(const std::string & keyword, const std::string & word_substitution, int precedence, const std::string & link_keyword) : rule_base(keyword, word_substitution, precedence), link_keyword_(link_keyword) {} virtual bool has_transformation() const { return true; } virtual action apply_transformation(stringlist & /*words*/, const tagmap & /*tags*/, std::string & link_keyword) { link_keyword = link_keyword_; return linkkey; } virtual std::string to_string() const { std::string sexp("("); sexp += keyword_; if (!word_substitution_.empty()) sexp += " = " + word_substitution_; if (precedence_ > 0) sexp += " " + std::to_string(precedence_); sexp += " (= " + link_keyword_ + "))\n"; return sexp; } private: std::string link_keyword_; }; class rule_pre : public rule_base { public: rule_pre() {} rule_pre(std::string keyword, int precedence, std::string word_substitution, stringlist decomposition, stringlist reassembly, std::string link_keyword) : rule_base(keyword, word_substitution, precedence), link_keyword_(link_keyword) { std::vector<stringlist> reassembly_rules; reassembly_rules.push_back(reassembly); add_transformation_rule(decomposition, reassembly_rules); } virtual bool has_transformation() const { return true; } virtual action apply_transformation(stringlist & words, const tagmap & tags, std::string & link_keyword) { if (trans_.size() != 1) return inapplicable; // bad script? stringlist constituents; if (!match(tags, trans_[0].decomposition, words, constituents)) return inapplicable; // [page 39 (f)] should not happen? if (trans_[0].reassembly_rules.size() != 1) return inapplicable; // bad script? stringlist & reassembly_rule = trans_[0].reassembly_rules[0]; // use the selected reassembly rule and decomposition components // to construct a response sentence words = reassemble(reassembly_rule, constituents); link_keyword = link_keyword_; return linkkey; } virtual std::string to_string() const { std::string sexp("("); sexp += keyword_; if (!word_substitution_.empty()) sexp += " = " + word_substitution_; sexp += "\n ((" + join(trans_[0].decomposition) + ")"; sexp += "\n (PRE (" + join(trans_[0].reassembly_rules[0]) + ") (=" + link_keyword_ + "))))\n"; return sexp; } private: std::string link_keyword_; }; class rule_memory : public rule_base { public: rule_memory() {} rule_memory(const std::string & keyword) : rule_base(keyword, "", 0) {} void create_memory(const std::string & keyword, const stringlist & words, const tagmap & tags) { if (keyword != keyword_) return; // JW says rules are selected at random [page 41 (f)] // But the ELIZA code shows that rules are actually selected via a HASH // function on the last word of the user's input text. assert(trans_.size() == num_transformations); const auto & transformation = trans_[hash(last_chunk_as_bcd(words.back()), 2)]; stringlist constituents; if (!match(tags, transformation.decomposition, words, constituents)) return; //std::cout << "Making memory[" << words.back() << "]: " << join(reassemble(transformation.reassembly_rules[0], constituents)) << "\n"; memories_.push_back(join(reassemble(transformation.reassembly_rules[0], constituents))); } // return true iff we have at least one saved memory bool memory_exists() const { return !memories_.empty(); } // return the next saved memory in the queue; remove it from the queue std::string recall_memory() { return memories_.empty() ? "" : pop_front(memories_); } virtual std::string to_string() const { std::string sexp("(MEMORY "); sexp += keyword_; for (const auto & k : trans_) { sexp += "\n (" + join(k.decomposition); sexp += " = " + join(k.reassembly_rules[0]) + ")"; } sexp += ")\n"; return sexp; } // the MEMORY rule must have this number of transformations static constexpr int num_transformations = 4; private: stringlist memories_; }; class rule_vanilla : public rule_base { public: rule_vanilla() {} rule_vanilla(const std::string & keyword, const std::string & word_substitution, int precedence) : rule_base(keyword, word_substitution, precedence) {} virtual bool has_transformation() const { return true; } virtual action apply_transformation(stringlist & words, const tagmap & tags, std::string & link_keyword) { if (trans_.empty()) return inapplicable; // bad script? stringlist constituents; auto rule = trans_.begin(); while (rule != trans_.end() && !match(tags, rule->decomposition, words, constituents)) ++rule; if (rule == trans_.end()) return inapplicable; // [page 39 (f)] should not happen? // get the next reassembly rule to be used for this decomposition rule stringlist & reassembly_rule = rule->reassembly_rules[rule->next_reassembly_rule]; // update the reassembly rule index so that they all get cycled through rule->next_reassembly_rule++; if (rule->next_reassembly_rule == rule->reassembly_rules.size()) rule->next_reassembly_rule = 0; // is it the special-case reassembly rule (NEWKEY)? if (reassembly_rule.size() == 1 && reassembly_rule[0] == "NEWKEY") return newkey; // yes, try the next highest priority keyword, if any // is it the special-case reassembly rule (=XXXX)? if (reassembly_rule.size() == 1 && reassembly_rule[0].size() > 1 && reassembly_rule[0][0] == '=') { link_keyword = reassembly_rule[0]; pop_front(link_keyword); // pop off the '=' return linkkey; // yes, try the next highest priority keyword, if any } // use the selected reassembly rule and decomposition components // to construct a response sentence words = reassemble(reassembly_rule, constituents); return complete; } virtual std::string to_string() const { std::string sexp("("); sexp += (keyword_ == SPECIAL_RULE_NONE) ? "NONE" : keyword_; if (!word_substitution_.empty()) sexp += " = " + word_substitution_; if (precedence_ > 0) sexp += " " + std::to_string(precedence_); for (const auto & k : trans_) { sexp += "\n ((" + join(k.decomposition) + ")"; for (const auto & r : k.reassembly_rules) sexp += "\n (" + join(r) + ")"; sexp += ")"; } sexp += ")\n"; return sexp; } }; // collect all tags from any of the given rules that have them into a tagmap tagmap collect_tags(const rulemap & rules) { tagmap tags; for (auto & r : rules) { stringlist keyword_tags(r.second->dlist_tags()); for (auto t : keyword_tags) { if (t == "/") continue; if (t.size() > 1 && t.front() == '/') pop_front(t); tags[t].push_back(r.second->keyword()); } } return tags; } template<typename T> auto get_rule(rulemap & rules, const std::string & keyword) { auto rule = rules.find(keyword); if (rule == rules.end()) { std::string msg("SCRIPT ERROR FOR "); msg += keyword; throw std::runtime_error(msg); } auto castrule = std::dynamic_pointer_cast<T>(rule->second); if (!castrule) { std::string msg("INTERNAL ERROR FOR "); msg += keyword; throw std::runtime_error(msg); } return castrule; } class eliza { public: eliza(const rulemap & rules) : rules_(rules), tags_(collect_tags(rules_)) {} eliza(rulemap && rules) : rules_(std::move(rules)), tags_(collect_tags(rules_)) {} // produce a response to the given 'input' using the given 'rules' std::string response(const std::string & input) { // for simplicity, convert the given input string to a list of uppercase words // e.g. "Hello, world!" -> (HELLO , WORLD !) stringlist words(split(filter_bcd(to_upper(input)))); // JW's "a certain counting mechanism" is updated for each response limit_ = limit_ % 4 + 1; // scan for keywords [page 38 (c)]; build the keystack; apply word substitutions stringlist keystack; int top_rank = 0; for (auto word = words.begin(); word != words.end(); ) { if (delimiter(*word)) { // keep only the first clause to contain a keyword [page 37 (c)] if (keystack.empty()) { // discard left of punctuation, continue scanning what remains word = words.erase(words.begin(), ++word); continue; } else { // discard right of punctuation, scan is complete word = words.erase(word, words.end()); break; } } const auto r = rules_.find(*word); if (r != rules_.end()) { const auto & rule = r->second; if (rule->has_transformation()) { if (rule->precedence() > top_rank) { // *word is a keyword with precedence higher than the highest // keyword found previously: it goes top of the keystack [page 39 (d)] keystack.push_front(*word); top_rank = rule->precedence(); } else { // *word is a keyword with precedence lower than the highest // keyword found previously: it goes bottom of the keystack keystack.push_back(*word); } } rule->apply_word_substitution(*word); // [page 39 (a)] } ++word; } auto memory_rule = get_rule<rule_memory>(rules_, SPECIAL_RULE_MEMORY); if (keystack.empty()) { /* a text without keywords; can we recall a MEMORY ? [page 41 (f)] JW's 1966 CACM paper refers to this decision as "a certain counting mechanism is in a particular state." The ELIZA code shows that the memory is recalled only when LIMIT has the value 4 */ if (limit_ == 4 && memory_rule->memory_exists()) return memory_rule->recall_memory(); } // the keystack contains all keywords that occur in the given 'input'; // apply transformation associated with the top keyword [page 39 (d)] while (!keystack.empty()) { const std::string top_keyword = pop_front(keystack); auto rule = rules_.find(top_keyword); if (rule == rules_.end()) { // e.g. could happen if a rule links to a non-existent keyword return nomatch_msgs_[limit_ - 1]; } // try to lay down a memory for future use memory_rule->create_memory(top_keyword, words, tags_); // perform the transformation for this rule std::string link_keyword; auto act = rule->second->apply_transformation(words, tags_, link_keyword); if (act == rule_base::complete) return join(words); // decomposition/reassembly successfully applied if (act == rule_base::inapplicable) { // no decomposition rule matched the input words; script error? return nomatch_msgs_[limit_ - 1]; } if (act == rule_base::linkkey) keystack.push_front(link_keyword); // rule links to another; loop // (rule_base::newkey -> rule wants to try next highest keyword, if any) assert(act == rule_base::linkkey || act == rule_base::newkey); } // last resort: the NONE rule never fails to produce a response [page 41 (d)] auto none_rule = get_rule<rule_vanilla>(rules_, SPECIAL_RULE_NONE); std::string discard; none_rule->apply_transformation(words, tags_, discard); return join(words); } private: // JW's "a certain counting mechanism," LIMIT, cycles through 1..4, then back to 1 int limit_{ 1 }; // the ELIZA script in 'rulemap' form rulemap rules_; // e.g. tags[BELIEF] -> (BELIEVE FEEL THINK WISH) // (This is derived from rules_. It's a member so we only need derive it once.) const tagmap tags_; // script error messages hard-coded in JW's ELIZA, selected by LIMIT (our limit_) const char * const nomatch_msgs_[4] = { "PLEASE CONTINUE", "HMMM", "GO ON, PLEASE", "I SEE" }; }; }//namespace elizalogic namespace elizascript { // reader for 1966 ELIZA script file format struct script { // ELIZA's opening remarks e.g. "HOW DO YOU DO. PLEASE TELL ME YOUR PROBLEM" stringlist hello_message; // maps keywords -> transformation rules elizalogic::rulemap rules; }; struct token { // types of token in an ELIZA script file enum class typ { eof, symbol, number, open_bracket, close_bracket }; typ t{ typ::eof }; std::string value; token() : t(typ::eof) {} token(typ t) : t(t) {} token(typ t, const std::string & value) : t(t), value(value) {} bool symbol() const { return t == typ::symbol; } bool symbol(const char * v) const { return t == typ::symbol && value == v; } bool number() const { return t == typ::number; } bool open() const { return t == typ::open_bracket; } bool close() const { return t == typ::close_bracket; } bool eof() const { return t == typ::eof; } bool operator==(const token & rhs) const { return t == rhs.t && value == rhs.value; } }; // this is just good enough to divide the ELIZA script file format // into tokens useful to eliza_script_reader template<typename T> class tokenizer { public: tokenizer(T & script_file) : stream_(script_file) {} // return the current token, but don't advance past it token peektok() { if (got_token_) return t_; got_token_ = true; return t_ = readtok(); } // return the current token, advance read point past it token nexttok() { if (got_token_) { got_token_ = false; return t_; } return readtok(); } private: T & stream_; token t_; bool got_token_{ false }; const static size_t buflen_ = 512; uint8_t buf_[buflen_]; size_t bufdata_{ 0 }; size_t bufptr_{ 0 }; token readtok() { uint8_t ch; for (;;) { do { // skip whitespace if (!nextch(ch)) return (token(token::typ::eof)); } while (is_whitespace(ch)); if (ch != ';') break; do { // skip comment if (!nextch(ch)) return (token(token::typ::eof)); } while (!is_newline(ch)); } if (ch == '(') return (token(token::typ::open_bracket)); if (ch == ')') return (token(token::typ::close_bracket)); if (is_digit(ch)) { token t(token::typ::number); t.value.push_back(ch); while (peekch(ch) && is_digit(ch)) { t.value.push_back(ch); nextch(ch); } return t; } // anything else is a symbol token t(token::typ::symbol); t.value.push_back(ch); while (peekch(ch) && !non_symbol(ch)) { t.value.push_back(ch); nextch(ch); } return t; } bool nextch(uint8_t & ch) { if (peekch(ch)) { ++bufptr_; return true; } return false; } bool peekch(uint8_t & ch) { if (bufptr_ == bufdata_) refilbuf(); if (bufptr_ == bufdata_) return false; ch = buf_[bufptr_]; return true; } void refilbuf() { bufptr_ = bufdata_ = 0; if (!stream_.eof()) { stream_.read(reinterpret_cast<char *>(buf_), buflen_); bufdata_ = static_cast<size_t>(stream_.gcount()); } } inline bool is_whitespace(uint8_t ch) { return ch <= 0x20 || ch == 0x7F; // this must hold: is_newline(ch) => is_whitespace(ch) } inline bool is_newline(uint8_t ch) { return ch == '\x0A' // LF || ch == '\x0B' // VT || ch == '\x0C' // FF || ch == '\x0D'; // CR } inline bool is_digit(uint8_t ch) { return unsigned(ch) - '0' < 10; } inline bool non_symbol(uint8_t ch) { return ch == '(' || ch == ')' || ch == ';' || is_whitespace(ch); } }; template<typename T> class eliza_script_reader { public: eliza_script_reader(T & script_file, script & s) : tok_(script_file), script_(s) { script_.hello_message = rdlist(); if (tok_.peektok().symbol("START")) tok_.nexttok(); // skip over START, if present while (read_rule()) ; } private: tokenizer<T> tok_; script & script_; // in the following comments, @ = position in symbol stream on function entry // return words between opening and closing brackets // if prior is true nexttok() should be the opening bracket, e.g. @(WORD WORD 0 WORD) // if prior is false nexttok() should be the first symbol following the // opening bracket, e.g. (@WORD WORD 0 WORD) stringlist rdlist(bool prior = true) { stringlist s; token t = tok_.nexttok(); if (prior) { if (!t.open()) throw std::runtime_error("expected '('"); t = tok_.nexttok(); } while (!t.close()) { if (t.t == token::typ::symbol) s.emplace_back(t.value); else if (t.number()) s.emplace_back(t.value); else if (t.open()) { // embed entire sublist in one sub-string std::string sublist; t = tok_.nexttok(); while (!t.close()) { if (!t.symbol()) throw std::runtime_error("expected symbol"); if (!sublist.empty()) sublist += ' '; sublist += t.value; t = tok_.nexttok(); } s.emplace_back("(" + sublist + ")"); } else throw std::runtime_error("expected ')'"); t = tok_.nexttok(); } return s; } /* e.g. (YOU'RE = I'M ((0 I'M 0) (@PRE (I ARE 3) (=YOU)))) */ bool read_pre_rule(const std::string & keyword, int precedence, const std::string & keyword_substitution, const stringlist & decomposition) { tok_.nexttok(); // skip PRE stringlist reassembly = rdlist(); if (!tok_.nexttok().open()) throw std::runtime_error("expected '('"); token t = tok_.nexttok(); std::string link_keyword; if (t.symbol("=")) { t = tok_.nexttok(); if (!t.symbol()) throw std::runtime_error("expected equivalence class name"); link_keyword = t.value; } else if (t.symbol() && t.value.size() > 1 && t.value[0] == '=') link_keyword = std::string(reinterpret_cast<const char *>(&t.value[1]), t.value.size() - 1); else throw std::runtime_error("expected equivalence class name"); if (!tok_.nexttok().close()) throw std::runtime_error("expected ')'"); if (!tok_.nexttok().close()) throw std::runtime_error("expected ')'"); if (!tok_.nexttok().close()) throw std::runtime_error("expected ')'"); if (!tok_.nexttok().close()) throw std::runtime_error("expected ')'"); script_.rules[keyword] = std::make_shared<elizalogic::rule_pre>(keyword, precedence, keyword_substitution, decomposition, reassembly, link_keyword); return true; } bool read_vanilla_rule(const std::string & keyword, const std::string & keyword_substitution, int precedence) // or PRE { auto r = std::make_shared<elizalogic::rule_vanilla>( keyword, keyword_substitution, precedence); for (;;) { const stringlist decomposition = rdlist(); std::vector<stringlist> reassembly_rules; token t = tok_.nexttok(); for (;;) { if (!t.open()) throw std::runtime_error("expected '('"); if (tok_.peektok().symbol("PRE")) return read_pre_rule(keyword, precedence, keyword_substitution, decomposition); reassembly_rules.push_back(rdlist(false)); t = tok_.nexttok(); if (t.close()) break; } r->add_transformation_rule(decomposition, reassembly_rules); t = tok_.nexttok(); if (t.close()) break; if (!t.open()) throw std::runtime_error("expected '('"); } script_.rules[keyword] = r; return true; } bool read_none() { tok_.nexttok(); return read_vanilla_rule(SPECIAL_RULE_NONE, "", 0); } bool read_memory() { token t = tok_.nexttok(); if (!t.symbol()) throw std::runtime_error("expected keyword"); auto r = std::make_shared<elizalogic::rule_memory>(t.value); for (int i = 0; i < elizalogic::rule_memory::num_transformations; ++i) { stringlist decomposition; std::vector<stringlist> reassembly_rules; if (!tok_.nexttok().open()) throw std::runtime_error("expected '('"); for (t = tok_.nexttok(); !t.symbol("=") && !t.eof(); t = tok_.nexttok()) decomposition.push_back(t.value); stringlist reassembly; for (t = tok_.nexttok(); !t.close() && !t.eof(); t = tok_.nexttok()) reassembly.push_back(t.value); reassembly_rules.push_back(reassembly); r->add_transformation_rule(decomposition, reassembly_rules); } if (!tok_.nexttok().close()) throw std::runtime_error("expected ')'"); script_.rules[SPECIAL_RULE_MEMORY] = r; return true; } // e.g. (MOM = MOTHER DLIST@(/ FAMILY)) bool dlist(const std::string & keyword, const std::string & keyword_substitution) { stringlist tags; tags = rdlist(); token t = tok_.nexttok(); if (!t.close()) throw std::runtime_error("expected ')'"); script_.rules[keyword] = std::make_shared<elizalogic::rule_dlist>(keyword, keyword_substitution, tags); return true; } // e.g. (DREAMED = DREAMT 4 (@=DREAMT)) bool read_equivalence_class(const std::string & keyword, const std::string & keyword_substitution, int precedence) { std::string class_name; token t = tok_.nexttok(); if (t.symbol("=")) { t = tok_.nexttok(); if (!t.symbol()) throw std::runtime_error("expected equivalence class name"); class_name = t.value; } else if (t.symbol() && t.value.size() > 1 && t.value[0] == '=') class_name = std::string(reinterpret_cast<const char *>(&t.value[1]), t.value.size() - 1); else throw std::runtime_error("expected equivalence class name"); script_.rules[keyword] = std::make_shared<elizalogic::rule_equivalence_class>( keyword, keyword_substitution, precedence, class_name); if (!tok_.nexttok().close()) throw std::runtime_error("expected ')'"); if (!tok_.nexttok().close()) throw std::runtime_error("expected ')'"); return true; } // read one rule of any type; return false => end of file reached bool read_rule() { std::string keyword, keyword_substitution; int precedence = 0; token t = tok_.nexttok(); if (t.t == token::typ::eof) return false; if (!t.open()) throw std::runtime_error("expected '('"); t = tok_.nexttok(); if (t.t == token::typ::close_bracket) return true; // ignore empty rule list, if present if (t.t != token::typ::symbol) throw std::runtime_error("expected keyword|MEMORY|NONE"); if (t.value == "MEMORY") return read_memory(); else if (t.value == "NONE") return read_none(); keyword = t.value; for (;;) { t = tok_.nexttok(); if (t.symbol("=")) { t = tok_.nexttok(); if (t.t != token::typ::symbol) throw std::runtime_error("expected keyword"); keyword_substitution = t.value; } else if (t.number()) precedence = std::stoi(t.value); else if (t.symbol("DLIST")) return dlist(keyword, keyword_substitution); else if (t.open()) { t = tok_.peektok(); if (t.symbol() && t.value[0] == '=') return read_equivalence_class(keyword, keyword_substitution, precedence); return read_vanilla_rule(keyword, keyword_substitution, precedence); } else if (t.t == token::typ::close_bracket) { // e.g. (ME = YOU) script_.rules[keyword] = std::make_shared<elizalogic::rule_unconditional_substitution>( keyword, keyword_substitution); return true; } else throw std::runtime_error("malformed rule"); } return true; } }; template<typename T> void read(T & script_file, script & s) { eliza_script_reader<T> reader(script_file, s); } const char * CACM_1966_01_DOCTOR_script = ";\n" "; APPENDIX. An Eliza Script\n" ";\n" "; Transcribed from Joseph Weizenbaum's article on page 36 of the January\n" "; 1966 edition of Communications of the ACM titled 'ELIZA - A Computer\n" "; Program For the Study of Natural Language Communication Between Man And\n" "; Machine'.\n" ";\n" "; \"Keywords and their associated transformation rules constitute the\n" "; SCRIPT for a particular class of conversation. An important property of\n" "; ELIZA is that a script is data; i.e., it is not part of the program\n" "; itself.\" -- From the above mentioned article.\n" ";\n" "; Transcribed by Anthony Hay, December 2020\n" ";\n" ";\n" "; Notes\n" ";\n" "; This is a verbatim transcription of the ELIZA script in the above\n" "; mentioned CACM article, with the following caveats:\n" "; a) Whitespace has been added to help reveal the structure of the\n" "; script.\n" "; b) In the appendix six lines were printed twice adjacent to each other\n" "; (with exactly 34 lines between each duplicate), making the structure\n" "; nonsensical. These duplicates have been commented out of this\n" "; transcription.\n" "; c) One closing bracket has been added and noted in a comment.\n" "; d) There were no comments in the script in the CACM article.\n" ";\n" ";\n" "; The script has the form of a series of S-expressions of varying\n" "; composition. (Weizenbaum says \"An ELIZA script consists mainly of a set\n" "; of list structures...\", but nowhere in the article are S-expressions or\n" "; LISP mentioned. Perhaps it was too obvious to be noted.) Weizenbaum says\n" "; ELIZA was written in MAD-SLIP. It seems his original source code has\n" "; been lost. Weizenbaum developed a library of FORTRAN functions for\n" "; manipulating doubly-linked lists, which he called SLIP (for Symmetric\n" "; list processor).\n" ";\n" "; The most common transformation rule has the form:\n" ";\n" "; (keyword [= replacement-keyword] [precedence]\n" "; [ ((decomposition-rule-0) (reassembly-rule-00) (reassembly-rule-01) ...)\n" "; ((decomposition-rule-1) (reassembly-rule-10) (reassembly-rule-11) ...)\n" "; ... ] )\n" ";\n" "; where [] denotes optional parts. Initially, ELIZA tries to match the\n" "; decomposition rules against the input text only for the highest ranked\n" "; keyword found in the input text. If a decomposition rule matches the\n" "; input text the first associated reassembly rule is used to generate\n" "; the output text. If there is more than one reassembly rule they are\n" "; used in turn on successive matches.\n" ";\n" "; In the decomposition rules '0' matches zero or more words in the input.\n" "; So (0 IF 0) matches \"IF POSSIBLE\" and \"WHAT IF YOU DIE\". Numbers in\n" "; the reassembly rules refer to the parts of the decomposition rule\n" "; match. 1 <empty>, 2 \"IF\", 3 \"POSSIBLE\" and 1 \"WHAT\", 2 \"IF\", 3 \"YOU DIE\"\n" "; in the above examples. If the selected reassembly rule was (DO YOU THINK\n" "; ITS LIKELY THAT 3) the text output would be \"DO YOU THINK ITS LIKELY\n" "; THAT YOU DIE\".\n" ";\n" ";\n" "; Each rule has one of the following six forms:\n" ";\n" "; R1. Plain vanilla transformation rule. [page 38 (a)]\n" "; (keyword [= keyword_substitution] [precedence]\n" "; [ ((decomposition_pattern) (reassembly_rule) (reassembly_rule) ... )\n" "; (decomposition_pattern) (reassembly_rule) (reassembly_rule) ... )\n" "; :\n" "; (decomposition_pattern) (reassembly_rule) (reassembly_rule) ... )) ] )\n" "; e.g.\n" "; (MY = YOUR 2\n" "; ((0 YOUR 0 (/FAMILY) 0)\n" "; (TELL ME MORE ABOUT YOUR FAMILY)\n" "; (WHO ELSE IN YOUR FAMILY 5)\n" "; (=WHAT)\n" "; (WHAT ELSE COMES TO MIND WHEN YOU THINK OF YOUR 4))\n" "; ((0 YOUR 0 (*SAD UNHAPPY DEPRESSED SICK ) 0)\n" "; (CAN YOU EXPLAIN WHAT MADE YOU 5))\n" "; ((0)\n" "; (NEWKEY)))\n" ";\n" ";\n" "; R2. Simple word substitution with no further transformation rules. [page 39 (a)]\n" "; (keyword = keyword_substitution)\n" "; e.g.\n" "; (DONT = DON'T)\n" "; (ME = YOU)\n" ";\n" ";\n" "; R3. Allow words to be given tags, with optional word substitution. [page 41 (j)]\n" "; (keyword [= keyword_substitution]\n" "; DLIST (/ <word> ... <word>))\n" "; e.g.\n" "; (FEEL DLIST(/BELIEF))\n" "; (MOTHER DLIST(/NOUN FAMILY))\n" "; (MOM = MOTHER DLIST(/ FAMILY))\n" ";\n" ";\n" "; R4. Link to another keyword transformation rule. [page 40 (c)]\n" "; (keyword [= keyword_substitution] [precedence]\n" "; (= equivalence_class))\n" "; e.g.\n" "; (HOW (=WHAT))\n" "; (WERE = WAS (=WAS))\n" "; (DREAMED = DREAMT 4 (=DREAMT))\n" "; (ALIKE 10 (=DIT))\n" ";\n" ";\n" "; R5. As for R4 but allow pre-transformation before link. [page 40 (f)]\n" "; (keyword [= keyword_substitution]\n" "; ((decomposition_pattern)\n" "; (PRE (reassembly_rule) (=equivalence_class))))\n" "; e.g.\n" "; (YOU'RE = I'M\n" "; ((0 I'M 0)\n" "; (PRE (I ARE 3) (=YOU))))\n" ";\n" ";\n" "; R6. Rule to 'pre-record' responses for later use. [page 41 (f)]\n" "; (MEMORY keyword\n" "; (decomposition_pattern_1 = reassembly_rule_1)\n" "; (decomposition_pattern_2 = reassembly_rule_2)\n" "; (decomposition_pattern_3 = reassembly_rule_3)\n" "; (decomposition_pattern_4 = reassembly_rule_4))\n" "; e.g.\n" "; (MEMORY MY\n" "; (0 YOUR 0 = LETS DISCUSS FURTHER WHY YOUR 3)\n" "; (0 YOUR 0 = EARLIER YOU SAID YOUR 3)\n" "; (0 YOUR 0 = BUT YOUR 3)\n" "; (0 YOUR 0 = DOES THAT HAVE ANYTHING TO DO WITH THE FACT THAT YOUR 3))\n" ";\n" ";\n" "; In addition, there must be a NONE rule with the same form as R1. [page 41 (d)]\n" "; (NONE\n" "; ((0)\n" "; (reassembly_rule)\n" "; (reassembly_rule)\n" "; :\n" "; (reassembly_rule)) )\n" "; e.g.\n" "; (NONE\n" "; ((0)\n" "; (I AM NOT SURE I UNDERSTAND YOU FULLY)\n" "; (PLEASE GO ON)\n" "; (WHAT DOES THAT SUGGEST TO YOU)\n" "; (DO YOU FEEL STRONGLY ABOUT DISCUSSING SUCH THINGS)))\n" ";\n" ";\n" "; For further details see Weizenbaum's article, or look at eliza.cpp.\n" ";\n" "\n" "\n" "(HOW DO YOU DO. PLEASE TELL ME YOUR PROBLEM)\n" "\n" "START\n" "\n" "(SORRY\n" " ((0)\n" " (PLEASE DON'T APOLIGIZE)\n" " (APOLOGIES ARE NOT NECESSARY)\n" " (WHAT FEELINGS DO YOU HAVE WHEN YOU APOLOGIZE)\n" " (I'VE TOLD YOU THAT APOLOGIES ARE NOT REQUIRED)))\n" "\n" "(DONT = DON'T)\n" "(CANT = CAN'T)\n" "(WONT = WON'T)\n" "\n" "(REMEMBER 5\n" " ((0 YOU REMEMBER 0)\n" " (DO YOU OFTEN THINK OF 4)\n" " (DOES THINKING OF 4 BRING ANYTHING ELSE TO MIND)\n" " (WHAT ELSE DO YOU REMEMBER)\n" " (WHY DO YOU REMEMBER 4 JUST NOW)\n" " (WHAT IN THE PRESENT SITUATION REMINDS YOU OF 4)\n" " (WHAT IS THE CONNECTION BETWEEN ME AND 4))\n" " ((0 DO I REMEMBER 0)\n" " (DID YOU THINK I WOULD FORGET 5)\n" " (WHY DO YOU THINK I SHOULD RECALL 5 NOW)\n" " (WHAT ABOUT 5)\n" " (=WHAT)\n" " (YOU MENTIONED 5))\n" " ((0)\n" " (NEWKEY)))\n" "\n" "(IF 3\n" " ((0 IF 0)\n" " (DO YOU THINK ITS LIKELY THAT 3)\n" " (DO YOU WISH THAT 3)\n" " (WHAT DO YOU THINK ABOUT 3)\n" " (REALLY, 2 3)))\n" "; duplicate line removed: (WHAT DO YOU THINK ABOUT 3) (REALLY, 2 3)))\n" "\n" "(DREAMT 4\n" " ((0 YOU DREAMT 0)\n" " (REALLY, 4)\n" " (HAVE YOU EVER FANTASIED 4 WHILE YOU WERE AWAKE)\n" " (HAVE YOU DREAMT 4 BEFORE)\n" " (=DREAM)\n" " (NEWKEY)))\n" "\n" "(DREAMED = DREAMT 4 (=DREAMT))\n" "\n" "(DREAM 3\n" " ((0)\n" " (WHAT DOES THAT DREAM SUGGEST TO YOU)\n" " (DO YOU DREAM OFTEN)\n" " (WHAT PERSONS APPEAR IN YOUR DREAMS)\n" " (DON'T YOU BELIEVE THAT DREAM HAS SOMETHING TO DO WITH YOUR PROBLEM)\n" " (NEWKEY)))\n" "\n" "(DREAMS = DREAM 3 (=DREAM))\n" "\n" "(HOW (=WHAT))\n" "(WHEN (=WHAT))\n" "(ALIKE 10 (=DIT))\n" "(SAME 10 (=DIT))\n" "(CERTAINLY (=YES))\n" "\n" "(FEEL DLIST(/BELIEF))\n" "(THINK DLIST(/BELIEF))\n" "(BELIEVE DLIST(/BELIEF))\n" "(WISH DLIST(/BELIEF))\n" "\n" "(MEMORY MY\n" " (0 YOUR 0 = LETS DISCUSS FURTHER WHY YOUR 3)\n" " (0 YOUR 0 = EARLIER YOU SAID YOUR 3)\n" " (0 YOUR 0 = BUT YOUR 3)\n" " (0 YOUR 0 = DOES THAT HAVE ANYTHING TO DO WITH THE FACT THAT YOUR 3))\n" "\n" "(NONE\n" " ((0)\n" " (I AM NOT SURE I UNDERSTAND YOU FULLY)\n" " (PLEASE GO ON)\n" " (WHAT DOES THAT SUGGEST TO YOU)\n" " (DO YOU FEEL STRONGLY ABOUT DISCUSSING SUCH THINGS)))\n" "\n" "(PERHAPS\n" " ((0)\n" " (YOU DON'T SEEM QUITE CERTAIN)\n" " (WHY THE UNCERTAIN TONE)\n" " (CAN'T YOU BE MORE POSITIVE)\n" " (YOU AREN'T SURE)\n" " (DON'T YOU KNOW)))\n" "\n" "(MAYBE (=PERHAPS))\n" "\n" "(NAME 15\n" " ((0)\n" " (I AM NOT INTERESTED IN NAMES)\n" " (I'VE TOLD YOU BEFORE, I DON'T CARE ABOUT NAMES - PLEASE CONTINUE)))\n" "; duplicate line removed: PLEASE CONTINUE)) )\n" "\n" "(DEUTSCH (=XFREMD))\n" "(FRANCAIS (=XFREMD))\n" "(ITALIANO (=XFREMD))\n" "(ESPANOL (=XFREMD))\n" "\n" "(XFREMD\n" " ((0)\n" " (I AM SORRY, I SPEAK ONLY ENGLISH)))\n" "\n" "(HELLO\n" " ((0)\n" " (HOW DO YOU DO. PLEASE STATE YOUR PROBLEM)))\n" "\n" "(COMPUTER 50\n" " ((0)\n" " (DO COMPUTERS WORRY YOU)\n" " (WHY DO YOU MENTION COMPUTERS)\n" " (WHAT DO YOU THINK MACHINES HAVE TO DO WITH YOUR PROBLEM)\n" " (DON'T YOU THINK COMPUTERS CAN HELP PEOPLE)\n" " (WHAT ABOUT MACHINES WORRIES YOU)\n" " (WHAT DO YOU THINK ABOUT MACHINES)))\n" "\n" "(MACHINE 50 (=COMPUTER))\n" "(MACHINES 50 (=COMPUTER))\n" "(COMPUTERS 50 (=COMPUTER))\n" "\n" "(AM = ARE\n" " ((0 ARE YOU 0)\n" " (DO YOU BELIEVE YOU ARE 4)\n" " (WOULD YOU WANT TO BE 4)\n" " (YOU WISH I WOULD TELL YOU YOU ARE 4)\n" " (WHAT WOULD IT MEAN IF YOU WERE 4)\n" " (=WHAT))\n" " ((0)\n" " (WHY DO YOU SAY 'AM')\n" " (I DON'T UNDERSTAND THAT)))\n" "\n" "(ARE\n" " ((0 ARE I 0)\n" " (WHY ARE YOU INTERESTED IN WHETHER I AM 4 OR NOT)\n" " (WOULD YOU PREFER IF I WEREN'T 4)\n" " (PERHAPS I AM 4 IN YOUR FANTASIES)\n" " (DO YOU SOMETIMES THINK I AM 4)\n" " (=WHAT))\n" " ((0 ARE 0)\n" " (DID YOU THINK THEY MIGHT NOT BE 3)\n" " (WOULD YOU LIKE IT IF THEY WERE NOT 3)\n" " (WHAT IF THEY WERE NOT 3)\n" " (POSSIBLY THEY ARE 3)))\n" "\n" "(YOUR = MY\n" " ((0 MY 0)\n" " (WHY ARE YOU CONCERNED OVER MY 3)\n" " (WHAT ABOUT YOUR OWN 3)\n" " (ARE YOU WORRIED ABOUT SOMEONE ELSES 3)\n" " (REALLY, MY 3)))\n" "\n" "(WAS 2\n" " ((0 WAS YOU 0)\n" " (WHAT IF YOU WERE 4)\n" " (DO YOU THINK YOU WERE 4)\n" " (WERE YOU 4)\n" " (WHAT WOULD IT MEAN IF YOU WERE 4)\n" " (WHAT DOES ' 4 ' SUGGEST TO YOU)\n" " (=WHAT))\n" " ((0 YOU WAS 0)\n" " (WERE YOU REALLY)\n" " (WHY DO YOU TELL ME YOU WERE 4 NOW)\n" "; duplicate line removed: (WERE YOU REALLY) (WHY DO YOU TELL ME YOU WERE 4 NOW)\n" " (PERHAPS I ALREADY KNEW YOU WERE 4))\n" " ((0 WAS I 0)\n" " (WOULD YOU LIKE TO BELIEVE I WAS 4)\n" " (WHAT SUGGESTS THAT I WAS 4)\n" " (WHAT DO YOU THINK)\n" " (PERHAPS I WAS 4)\n" " (WHAT IF I HAD BEEN 4))\n" " ((0)\n" " (NEWKEY)))\n" "\n" "(WERE = WAS (=WAS))\n" "(ME = YOU)\n" "\n" "(YOU'RE = I'M\n" " ((0 I'M 0)\n" " (PRE (I ARE 3) (=YOU))))\n" "\n" "(I'M = YOU'RE\n" " ((0 YOU'RE 0)\n" " (PRE (YOU ARE 3) (=I))))\n" "\n" "(MYSELF = YOURSELF)\n" "(YOURSELF = MYSELF)\n" "\n" "(MOTHER DLIST(/NOUN FAMILY))\n" "(MOM = MOTHER DLIST(/ FAMILY))\n" "(DAD = FATHER DLIST(/ FAMILY))\n" "(FATHER DLIST(/NOUN FAMILY))\n" "(SISTER DLIST(/FAMILY))\n" "(BROTHER DLIST(/FAMILY))\n" "(WIFE DLIST(/FAMILY))\n" "(CHILDREN DLIST(/FAMILY))\n" "\n" "(I = YOU\n" " ((0 YOU (* WANT NEED) 0)\n" " (WHAT WOULD IT MEAN TO YOU IF YOU GOT 4)\n" " (WHY DO YOU WANT 4)\n" " (SUPPOSE YOU GOT 4 SOON)\n" " (WHAT IF YOU NEVER GOT 4)\n" " (WHAT WOULD GETTING 4 MEAN TO YOU)\n" " (WHAT DOES WANTING 4 HAVE TO DO WITH THIS DISCUSSION))\n" " ((0 YOU ARE 0 (*SAD UNHAPPY DEPRESSED SICK ) 0)\n" " (I AM SORRY TO HEAR YOU ARE 5)\n" " (DO YOU THINK COMING HERE WILL HELP YOU NOT TO BE 5)\n" " (I'M SURE ITS NOT PLEASANT TO BE 5)\n" " (CAN YOU EXPLAIN WHAT MADE YOU 5))\n" " ((0 YOU ARE 0 (*HAPPY ELATED GLAD BETTER) 0)\n" " (HOW HAVE I HELPED YOU TO BE 5)\n" " (HAS YOUR TREATMENT MADE YOU 5)\n" " (WHAT MAKES YOU 5 JUST NOW)\n" " (CAN YOU EXPLAIN WHY YOU ARE SUDDENLY 5))\n" " ((0 YOU WAS 0)\n" " (=WAS))\n" "; duplicate line removed: ((0 YOU WAS 0) (=WAS))\n" " ((0 YOU (/BELIEF) YOU 0)\n" " (DO YOU REALLY THINK SO)\n" " (BUT YOU ARE NOT SURE YOU 5)\n" " (DO YOU REALLY DOUBT YOU 5))\n" " ((0 YOU 0 (/BELIEF) 0 I 0)\n" " (=YOU))\n" " ((0 YOU ARE 0)\n" " (IS IT BECAUSE YOU ARE 4 THAT YOU CAME TO ME)\n" " (HOW LONG HAVE YOU BEEN 4)\n" " (DO YOU BELIEVE IT NORMAL TO BE 4)\n" " (DO YOU ENJOY BEING 4))\n" " ((0 YOU (* CAN'T CANNOT) 0)\n" " (HOW DO YOU KNOW YOU CAN'T 4)\n" " (HAVE YOU TRIED)\n" " (PERHAPS YOU COULD 4 NOW)\n" " (DO YOU REALLY WANT TO BE ABLE TO 4))\n" " ((0 YOU DON'T 0)\n" " (DON'T YOU REALLY 4)\n" " (WHY DON'T YOU 4)\n" " (DO YOU WISH TO BE ABLE TO 4)\n" " (DOES THAT TROUBLE YOU))\n" " ((0 YOU FEEL 0)\n" " (TELL ME MORE ABOUT SUCH FEELINGS)\n" " (DO YOU OFTEN FEEL 4)\n" " (DO YOU ENJOY FEELING 4)\n" " (OF WHAT DOES FEELING 4 REMIND YOU))\n" " ((0 YOU 0 I 0)\n" " (PERHAPS IN YOUR FANTASY WE 3 EACH OTHER)\n" " (DO YOU WISH TO 3 ME)\n" " (YOU SEEM TO NEED TO 3 ME)\n" " (DO YOU 3 ANYONE ELSE))\n" " ((0)\n" " (YOU SAY 1)\n" " (CAN YOU ELABORATE ON THAT)\n" " (DO YOU SAY 1 FOR SOME SPECIAL REASON)\n" " (THAT'S QUITE INTERESTING)))\n" "\n" "(YOU = I\n" " ((0 I REMIND YOU OF 0)\n" " (=DIT))\n" " ((0 I ARE 0)\n" " (WHAT MAKES YOU THINK I AM 4)\n" " (DOES IT PLEASE YOU TO BELIEVE I AM 4)\n" " (DO YOU SOMETIMES WISH YOU WERE 4)\n" " (PERHAPS YOU WOULD LIKE TO BE 4))\n" " ((0 I 0 YOU)\n" " (WHY DO YOU THINK I 3 YOU)\n" " (YOU LIKE TO THINK I 3 YOU - DON'T YOU)\n" " (WHAT MAKES YOU THINK I 3 YOU)\n" " (REALLY, I 3 YOU)\n" " (DO YOU WISH TO BELIEVE I 3 YOU)\n" "; duplicate line removed: (REALLY, I 3 YOU) (DO YOU WISH TO BELIEVE I 3 YOU)\n" " (SUPPOSE I DID 3 YOU - WHAT WOULD THAT MEAN)\n" " (DOES SOMEONE ELSE BELIEVE I 3 YOU))\n" " ((0 I 0)\n" " (WE WERE DISCUSSING YOU - NOT ME)\n" " (OH, I 3)\n" " (YOU'RE NOT REALLY TALKING ABOUT ME - ARE YOU)\n" " (WHAT ARE YOUR FEELINGS NOW)))\n" "\n" "(YES\n" " ((0)\n" " (YOU SEEM QUITE POSITIVE)\n" " (YOU ARE SURE)\n" " (I SEE)\n" " (I UNDERSTAND)))\n" "\n" "(NO\n" " ((0)\n" " (ARE YOU SAYING 'NO' JUST TO BE NEGATIVE)\n" " (YOU ARE BEING A BIT NEGATIVE)\n" " (WHY NOT)\n" " (WHY 'NO')))\n" "\n" "(MY = YOUR 2\n" " ((0 YOUR 0 (/FAMILY) 0)\n" " (TELL ME MORE ABOUT YOUR FAMILY)\n" " (WHO ELSE IN YOUR FAMILY 5)\n" " (YOUR 4)\n" " (WHAT ELSE COMES TO MIND WHEN YOU THINK OF YOUR 4))\n" " ((0 YOUR 0)\n" " (YOUR 3)\n" " (WHY DO YOU SAY YOUR 3)\n" " (DOES THAT SUGGEST ANYTHING ELSE WHICH BELONGS TO YOU)\n" " (IS IT IMPORTANT TO YOU THAT 2 3)))\n" "\n" "(CAN\n" " ((0 CAN I 0)\n" " (YOU BELIEVE I CAN 4 DON'T YOU)\n" " (=WHAT)\n" " (YOU WANT ME TO BE ABLE TO 4)\n" " (PERHAPS YOU WOULD LIKE TO BE ABLE TO 4 YOURSELF))\n" " ((0 CAN YOU 0)\n" " (WHETHER OR NOT YOU CAN 4 DEPENDS ON YOU MORE THAN ON ME)\n" " (DO YOU WANT TO BE ABLE TO 4)\n" " (PERHAPS YOU DON'T WANT TO 4)\n" " (=WHAT)))\n" "\n" "(WHAT\n" " ((0)\n" " (WHY DO YOU ASK)\n" " (DOES THAT QUESTION INTEREST YOU)\n" " (WHAT IS IT YOU REALLY WANT TO KNOW)\n" " (ARE SUCH QUESTIONS MUCH ON YOUR MIND)\n" " (WHAT ANSWER WOULD PLEASE YOU MOST)\n" " (WHAT DO YOU THINK)\n" " (WHAT COMES TO YOUR MIND WHEN YOU ASK THAT)\n" " (HAVE YOU ASKED SUCH QUESTIONS BEFORE)\n" " (HAVE YOU ASKED ANYONE ELSE)))\n" "\n" "(BECAUSE\n" " ((0)\n" " (IS THAT THE REAL REASON)\n" " (DON'T ANY OTHER REASONS COME TO MIND)\n" " (DOES THAT REASON SEEM TO EXPLAIN ANYTHING ELSE)\n" " (WHAT OTHER REASONS MIGHT THERE BE)))\n" "\n" "(WHY\n" " ((0 WHY DON'T I 0)\n" " (DO YOU BELIEVE I DON'T 5)\n" " (PERHAPS I WILL 5 IN GOOD TIME)\n" " (SHOULD YOU 5 YOURSELF)\n" " (YOU WANT ME TO 5)\n" " (=WHAT))\n" "; duplicate line removed: (=WHAT))\n" " ((0 WHY CAN'T YOU 0)\n" " (DO YOU THINK YOU SHOULD BE ABLE TO 5)\n" " (DO YOU WANT TO BE ABLE TO 5)\n" " (DO YOU BELIEVE THIS WILL HELP YOU TO 5)\n" " (HAVE YOU ANY IDEA WHY YOU CAN'T 5)\n" " (=WHAT)))\n" "; extraneous (=WHAT)) removed\n" "; and missing final ')' added\n" "\n" "(EVERYONE 2\n" " ((0 (* EVERYONE EVERYBODY NOBODY NOONE) 0)\n" " (REALLY, 2)\n" " (SURELY NOT 2)\n" " (CAN YOU THINK OF ANYONE IN PARTICULAR)\n" " (WHO, FOR EXAMPLE)\n" " (YOU ARE THINKING OF A VERY SPECIAL PERSON)\n" " (WHO, MAY I ASK)\n" " (SOMEONE SPECIAL PERHAPS)\n" " (YOU HAVE A PARTICULAR PERSON IN MIND, DON'T YOU)\n" " (WHO DO YOU THINK YOU'RE TALKING ABOUT)))\n" "\n" "(EVERYBODY 2 (= EVERYONE))\n" "(NOBODY 2 (= EVERYONE))\n" "(NOONE 2 (= EVERYONE))\n" "\n" "(ALWAYS 1\n" " ((0)\n" " (CAN YOU THINK OF A SPECIFIC EXAMPLE)\n" " (WHEN)\n" " (WHAT INCIDENT ARE YOU THINKING OF)\n" " (REALLY, ALWAYS)))\n" "\n" "(LIKE 10\n" " ((0 (*AM IS ARE WAS) 0 LIKE 0)\n" " (=DIT))\n" " ((0)\n" " (NEWKEY)))\n" "\n" "(DIT\n" " ((0)\n" " (IN WHAT WAY)\n" " (WHAT RESEMBLANCE DO YOU SEE)\n" " (WHAT DOES THAT SIMILARITY SUGGEST TO YOU)\n" " (WHAT OTHER CONNECTIONS DO YOU SEE)\n" " (WHAT DO YOU SUPPOSE THAT RESEMBLANCE MEANS)\n" " (WHAT IS THE CONNECTION, DO YOU SUPPOSE)\n" " (COULD THERE REALLY BE SOME CONNECTION)\n" " (HOW)))\n" "\n" "()\n" "\n" "; --- End of ELIZA script ---\n"; }//namespace elizascript namespace elizatest { // basic test of whether this simulation is accurate // return a string in ELIZA script format representing given script s std::string to_string(const elizascript::script & s) { std::string result; result += "(" + join(s.hello_message) + ")\n"; result += "START\n"; for (const auto & r : s.rules) result += r.second->to_string(); result += "()\n"; return result; } // perform basic checks on implementation DEF_TEST_FUNC(script_and_conversation_test) { // script_text is logically identical to the script in the CACM article // appendix, but the ordering and whitespace is different so that it can // be checked against the output of elizatest::to_string() const char * script_text = "(HOW DO YOU DO. PLEASE TELL ME YOUR PROBLEM)\n" "START\n" "(ALIKE 10 (= DIT))\n" "(ALWAYS 1\n" " ((0)\n" " (CAN YOU THINK OF A SPECIFIC EXAMPLE)\n" " (WHEN)\n" " (WHAT INCIDENT ARE YOU THINKING OF)\n" " (REALLY, ALWAYS)))\n" "(AM = ARE\n" " ((0 ARE YOU 0)\n" " (DO YOU BELIEVE YOU ARE 4)\n" " (WOULD YOU WANT TO BE 4)\n" " (YOU WISH I WOULD TELL YOU YOU ARE 4)\n" " (WHAT WOULD IT MEAN IF YOU WERE 4)\n" " (=WHAT))\n" " ((0)\n" " (WHY DO YOU SAY 'AM')\n" " (I DON'T UNDERSTAND THAT)))\n" "(ARE\n" " ((0 ARE I 0)\n" " (WHY ARE YOU INTERESTED IN WHETHER I AM 4 OR NOT)\n" " (WOULD YOU PREFER IF I WEREN'T 4)\n" " (PERHAPS I AM 4 IN YOUR FANTASIES)\n" " (DO YOU SOMETIMES THINK I AM 4)\n" " (=WHAT))\n" " ((0 ARE 0)\n" " (DID YOU THINK THEY MIGHT NOT BE 3)\n" " (WOULD YOU LIKE IT IF THEY WERE NOT 3)\n" " (WHAT IF THEY WERE NOT 3)\n" " (POSSIBLY THEY ARE 3)))\n" "(BECAUSE\n" " ((0)\n" " (IS THAT THE REAL REASON)\n" " (DON'T ANY OTHER REASONS COME TO MIND)\n" " (DOES THAT REASON SEEM TO EXPLAIN ANYTHING ELSE)\n" " (WHAT OTHER REASONS MIGHT THERE BE)))\n" "(BELIEVE DLIST(/BELIEF))\n" "(BROTHER DLIST(/FAMILY))\n" "(CAN\n" " ((0 CAN I 0)\n" " (YOU BELIEVE I CAN 4 DON'T YOU)\n" " (=WHAT)\n" " (YOU WANT ME TO BE ABLE TO 4)\n" " (PERHAPS YOU WOULD LIKE TO BE ABLE TO 4 YOURSELF))\n" " ((0 CAN YOU 0)\n" " (WHETHER OR NOT YOU CAN 4 DEPENDS ON YOU MORE THAN ON ME)\n" " (DO YOU WANT TO BE ABLE TO 4)\n" " (PERHAPS YOU DON'T WANT TO 4)\n" " (=WHAT)))\n" "(CANT = CAN'T)\n" "(CERTAINLY (= YES))\n" "(CHILDREN DLIST(/FAMILY))\n" "(COMPUTER 50\n" " ((0)\n" " (DO COMPUTERS WORRY YOU)\n" " (WHY DO YOU MENTION COMPUTERS)\n" " (WHAT DO YOU THINK MACHINES HAVE TO DO WITH YOUR PROBLEM)\n" " (DON'T YOU THINK COMPUTERS CAN HELP PEOPLE)\n" " (WHAT ABOUT MACHINES WORRIES YOU)\n" " (WHAT DO YOU THINK ABOUT MACHINES)))\n" "(COMPUTERS 50 (= COMPUTER))\n" "(DAD = FATHER DLIST(/ FAMILY))\n" "(DEUTSCH (= XFREMD))\n" "(DIT\n" " ((0)\n" " (IN WHAT WAY)\n" " (WHAT RESEMBLANCE DO YOU SEE)\n" " (WHAT DOES THAT SIMILARITY SUGGEST TO YOU)\n" " (WHAT OTHER CONNECTIONS DO YOU SEE)\n" " (WHAT DO YOU SUPPOSE THAT RESEMBLANCE MEANS)\n" " (WHAT IS THE CONNECTION, DO YOU SUPPOSE)\n" " (COULD THERE REALLY BE SOME CONNECTION)\n" " (HOW)))\n" "(DONT = DON'T)\n" "(DREAM 3\n" " ((0)\n" " (WHAT DOES THAT DREAM SUGGEST TO YOU)\n" " (DO YOU DREAM OFTEN)\n" " (WHAT PERSONS APPEAR IN YOUR DREAMS)\n" " (DON'T YOU BELIEVE THAT DREAM HAS SOMETHING TO DO WITH YOUR PROBLEM)\n" " (NEWKEY)))\n" "(DREAMED = DREAMT 4 (= DREAMT))\n" "(DREAMS = DREAM 3 (= DREAM))\n" "(DREAMT 4\n" " ((0 YOU DREAMT 0)\n" " (REALLY, 4)\n" " (HAVE YOU EVER FANTASIED 4 WHILE YOU WERE AWAKE)\n" " (HAVE YOU DREAMT 4 BEFORE)\n" " (=DREAM)\n" " (NEWKEY)))\n" "(ESPANOL (= XFREMD))\n" "(EVERYBODY 2 (= EVERYONE))\n" "(EVERYONE 2\n" " ((0 (* EVERYONE EVERYBODY NOBODY NOONE) 0)\n" " (REALLY, 2)\n" " (SURELY NOT 2)\n" " (CAN YOU THINK OF ANYONE IN PARTICULAR)\n" " (WHO, FOR EXAMPLE)\n" " (YOU ARE THINKING OF A VERY SPECIAL PERSON)\n" " (WHO, MAY I ASK)\n" " (SOMEONE SPECIAL PERHAPS)\n" " (YOU HAVE A PARTICULAR PERSON IN MIND, DON'T YOU)\n" " (WHO DO YOU THINK YOU'RE TALKING ABOUT)))\n" "(FATHER DLIST(/NOUN FAMILY))\n" "(FEEL DLIST(/BELIEF))\n" "(FRANCAIS (= XFREMD))\n" "(HELLO\n" " ((0)\n" " (HOW DO YOU DO. PLEASE STATE YOUR PROBLEM)))\n" "(HOW (= WHAT))\n" "(I = YOU\n" " ((0 YOU (* WANT NEED) 0)\n" " (WHAT WOULD IT MEAN TO YOU IF YOU GOT 4)\n" " (WHY DO YOU WANT 4)\n" " (SUPPOSE YOU GOT 4 SOON)\n" " (WHAT IF YOU NEVER GOT 4)\n" " (WHAT WOULD GETTING 4 MEAN TO YOU)\n" " (WHAT DOES WANTING 4 HAVE TO DO WITH THIS DISCUSSION))\n" " ((0 YOU ARE 0 (*SAD UNHAPPY DEPRESSED SICK) 0)\n" " (I AM SORRY TO HEAR YOU ARE 5)\n" " (DO YOU THINK COMING HERE WILL HELP YOU NOT TO BE 5)\n" " (I'M SURE ITS NOT PLEASANT TO BE 5)\n" " (CAN YOU EXPLAIN WHAT MADE YOU 5))\n" " ((0 YOU ARE 0 (*HAPPY ELATED GLAD BETTER) 0)\n" " (HOW HAVE I HELPED YOU TO BE 5)\n" " (HAS YOUR TREATMENT MADE YOU 5)\n" " (WHAT MAKES YOU 5 JUST NOW)\n" " (CAN YOU EXPLAIN WHY YOU ARE SUDDENLY 5))\n" " ((0 YOU WAS 0)\n" " (=WAS))\n" " ((0 YOU (/BELIEF) YOU 0)\n" " (DO YOU REALLY THINK SO)\n" " (BUT YOU ARE NOT SURE YOU 5)\n" " (DO YOU REALLY DOUBT YOU 5))\n" " ((0 YOU 0 (/BELIEF) 0 I 0)\n" " (=YOU))\n" " ((0 YOU ARE 0)\n" " (IS IT BECAUSE YOU ARE 4 THAT YOU CAME TO ME)\n" " (HOW LONG HAVE YOU BEEN 4)\n" " (DO YOU BELIEVE IT NORMAL TO BE 4)\n" " (DO YOU ENJOY BEING 4))\n" " ((0 YOU (* CAN'T CANNOT) 0)\n" " (HOW DO YOU KNOW YOU CAN'T 4)\n" " (HAVE YOU TRIED)\n" " (PERHAPS YOU COULD 4 NOW)\n" " (DO YOU REALLY WANT TO BE ABLE TO 4))\n" " ((0 YOU DON'T 0)\n" " (DON'T YOU REALLY 4)\n" " (WHY DON'T YOU 4)\n" " (DO YOU WISH TO BE ABLE TO 4)\n" " (DOES THAT TROUBLE YOU))\n" " ((0 YOU FEEL 0)\n" " (TELL ME MORE ABOUT SUCH FEELINGS)\n" " (DO YOU OFTEN FEEL 4)\n" " (DO YOU ENJOY FEELING 4)\n" " (OF WHAT DOES FEELING 4 REMIND YOU))\n" " ((0 YOU 0 I 0)\n" " (PERHAPS IN YOUR FANTASY WE 3 EACH OTHER)\n" " (DO YOU WISH TO 3 ME)\n" " (YOU SEEM TO NEED TO 3 ME)\n" " (DO YOU 3 ANYONE ELSE))\n" " ((0)\n" " (YOU SAY 1)\n" " (CAN YOU ELABORATE ON THAT)\n" " (DO YOU SAY 1 FOR SOME SPECIAL REASON)\n" " (THAT'S QUITE INTERESTING)))\n" "(I'M = YOU'RE\n" " ((0 YOU'RE 0)\n" " (PRE (YOU ARE 3) (=I))))\n" "(IF 3\n" " ((0 IF 0)\n" " (DO YOU THINK ITS LIKELY THAT 3)\n" " (DO YOU WISH THAT 3)\n" " (WHAT DO YOU THINK ABOUT 3)\n" " (REALLY, 2 3)))\n" "(ITALIANO (= XFREMD))\n" "(LIKE 10\n" " ((0 (*AM IS ARE WAS) 0 LIKE 0)\n" " (=DIT))\n" " ((0)\n" " (NEWKEY)))\n" "(MACHINE 50 (= COMPUTER))\n" "(MACHINES 50 (= COMPUTER))\n" "(MAYBE (= PERHAPS))\n" "(ME = YOU)\n" "(MOM = MOTHER DLIST(/ FAMILY))\n" "(MOTHER DLIST(/NOUN FAMILY))\n" "(MY = YOUR 2\n" " ((0 YOUR 0 (/FAMILY) 0)\n" " (TELL ME MORE ABOUT YOUR FAMILY)\n" " (WHO ELSE IN YOUR FAMILY 5)\n" " (YOUR 4)\n" " (WHAT ELSE COMES TO MIND WHEN YOU THINK OF YOUR 4))\n" " ((0 YOUR 0)\n" " (YOUR 3)\n" " (WHY DO YOU SAY YOUR 3)\n" " (DOES THAT SUGGEST ANYTHING ELSE WHICH BELONGS TO YOU)\n" " (IS IT IMPORTANT TO YOU THAT 2 3)))\n" "(MYSELF = YOURSELF)\n" "(NAME 15\n" " ((0)\n" " (I AM NOT INTERESTED IN NAMES)\n" " (I'VE TOLD YOU BEFORE, I DON'T CARE ABOUT NAMES - PLEASE CONTINUE)))\n" "(NO\n" " ((0)\n" " (ARE YOU SAYING 'NO' JUST TO BE NEGATIVE)\n" " (YOU ARE BEING A BIT NEGATIVE)\n" " (WHY NOT)\n" " (WHY 'NO')))\n" "(NOBODY 2 (= EVERYONE))\n" "(NOONE 2 (= EVERYONE))\n" "(PERHAPS\n" " ((0)\n" " (YOU DON'T SEEM QUITE CERTAIN)\n" " (WHY THE UNCERTAIN TONE)\n" " (CAN'T YOU BE MORE POSITIVE)\n" " (YOU AREN'T SURE)\n" " (DON'T YOU KNOW)))\n" "(REMEMBER 5\n" " ((0 YOU REMEMBER 0)\n" " (DO YOU OFTEN THINK OF 4)\n" " (DOES THINKING OF 4 BRING ANYTHING ELSE TO MIND)\n" " (WHAT ELSE DO YOU REMEMBER)\n" " (WHY DO YOU REMEMBER 4 JUST NOW)\n" " (WHAT IN THE PRESENT SITUATION REMINDS YOU OF 4)\n" " (WHAT IS THE CONNECTION BETWEEN ME AND 4))\n" " ((0 DO I REMEMBER 0)\n" " (DID YOU THINK I WOULD FORGET 5)\n" " (WHY DO YOU THINK I SHOULD RECALL 5 NOW)\n" " (WHAT ABOUT 5)\n" " (=WHAT)\n" " (YOU MENTIONED 5))\n" " ((0)\n" " (NEWKEY)))\n" "(SAME 10 (= DIT))\n" "(SISTER DLIST(/FAMILY))\n" "(SORRY\n" " ((0)\n" " (PLEASE DON'T APOLIGIZE)\n" " (APOLOGIES ARE NOT NECESSARY)\n" " (WHAT FEELINGS DO YOU HAVE WHEN YOU APOLOGIZE)\n" " (I'VE TOLD YOU THAT APOLOGIES ARE NOT REQUIRED)))\n" "(THINK DLIST(/BELIEF))\n" "(WAS 2\n" " ((0 WAS YOU 0)\n" " (WHAT IF YOU WERE 4)\n" " (DO YOU THINK YOU WERE 4)\n" " (WERE YOU 4)\n" " (WHAT WOULD IT MEAN IF YOU WERE 4)\n" " (WHAT DOES ' 4 ' SUGGEST TO YOU)\n" " (=WHAT))\n" " ((0 YOU WAS 0)\n" " (WERE YOU REALLY)\n" " (WHY DO YOU TELL ME YOU WERE 4 NOW)\n" " (PERHAPS I ALREADY KNEW YOU WERE 4))\n" " ((0 WAS I 0)\n" " (WOULD YOU LIKE TO BELIEVE I WAS 4)\n" " (WHAT SUGGESTS THAT I WAS 4)\n" " (WHAT DO YOU THINK)\n" " (PERHAPS I WAS 4)\n" " (WHAT IF I HAD BEEN 4))\n" " ((0)\n" " (NEWKEY)))\n" "(WERE = WAS (= WAS))\n" "(WHAT\n" " ((0)\n" " (WHY DO YOU ASK)\n" " (DOES THAT QUESTION INTEREST YOU)\n" " (WHAT IS IT YOU REALLY WANT TO KNOW)\n" " (ARE SUCH QUESTIONS MUCH ON YOUR MIND)\n" " (WHAT ANSWER WOULD PLEASE YOU MOST)\n" " (WHAT DO YOU THINK)\n" " (WHAT COMES TO YOUR MIND WHEN YOU ASK THAT)\n" " (HAVE YOU ASKED SUCH QUESTIONS BEFORE)\n" " (HAVE YOU ASKED ANYONE ELSE)))\n" "(WHEN (= WHAT))\n" "(WHY\n" " ((0 WHY DON'T I 0)\n" " (DO YOU BELIEVE I DON'T 5)\n" " (PERHAPS I WILL 5 IN GOOD TIME)\n" " (SHOULD YOU 5 YOURSELF)\n" " (YOU WANT ME TO 5)\n" " (=WHAT))\n" " ((0 WHY CAN'T YOU 0)\n" " (DO YOU THINK YOU SHOULD BE ABLE TO 5)\n" " (DO YOU WANT TO BE ABLE TO 5)\n" " (DO YOU BELIEVE THIS WILL HELP YOU TO 5)\n" " (HAVE YOU ANY IDEA WHY YOU CAN'T 5)\n" " (=WHAT)))\n" "(WIFE DLIST(/FAMILY))\n" "(WISH DLIST(/BELIEF))\n" "(WONT = WON'T)\n" "(XFREMD\n" " ((0)\n" " (I AM SORRY, I SPEAK ONLY ENGLISH)))\n" "(YES\n" " ((0)\n" " (YOU SEEM QUITE POSITIVE)\n" " (YOU ARE SURE)\n" " (I SEE)\n" " (I UNDERSTAND)))\n" "(YOU = I\n" " ((0 I REMIND YOU OF 0)\n" " (=DIT))\n" " ((0 I ARE 0)\n" " (WHAT MAKES YOU THINK I AM 4)\n" " (DOES IT PLEASE YOU TO BELIEVE I AM 4)\n" " (DO YOU SOMETIMES WISH YOU WERE 4)\n" " (PERHAPS YOU WOULD LIKE TO BE 4))\n" " ((0 I 0 YOU)\n" " (WHY DO YOU THINK I 3 YOU)\n" " (YOU LIKE TO THINK I 3 YOU - DON'T YOU)\n" " (WHAT MAKES YOU THINK I 3 YOU)\n" " (REALLY, I 3 YOU)\n" " (DO YOU WISH TO BELIEVE I 3 YOU)\n" " (SUPPOSE I DID 3 YOU - WHAT WOULD THAT MEAN)\n" " (DOES SOMEONE ELSE BELIEVE I 3 YOU))\n" " ((0 I 0)\n" " (WE WERE DISCUSSING YOU - NOT ME)\n" " (OH, I 3)\n" " (YOU'RE NOT REALLY TALKING ABOUT ME - ARE YOU)\n" " (WHAT ARE YOUR FEELINGS NOW)))\n" "(YOU'RE = I'M\n" " ((0 I'M 0)\n" " (PRE (I ARE 3) (=YOU))))\n" "(YOUR = MY\n" " ((0 MY 0)\n" " (WHY ARE YOU CONCERNED OVER MY 3)\n" " (WHAT ABOUT YOUR OWN 3)\n" " (ARE YOU WORRIED ABOUT SOMEONE ELSES 3)\n" " (REALLY, MY 3)))\n" "(YOURSELF = MYSELF)\n" "(MEMORY MY\n" " (0 YOUR 0 = LETS DISCUSS FURTHER WHY YOUR 3)\n" " (0 YOUR 0 = EARLIER YOU SAID YOUR 3)\n" " (0 YOUR 0 = BUT YOUR 3)\n" " (0 YOUR 0 = DOES THAT HAVE ANYTHING TO DO WITH THE FACT THAT YOUR 3))\n" "(NONE\n" " ((0)\n" " (I AM NOT SURE I UNDERSTAND YOU FULLY)\n" " (PLEASE GO ON)\n" " (WHAT DOES THAT SUGGEST TO YOU)\n" " (DO YOU FEEL STRONGLY ABOUT DISCUSSING SUCH THINGS)))\n" "()\n"; std::stringstream ss(elizascript::CACM_1966_01_DOCTOR_script); elizascript::script s; elizascript::read<std::stringstream>(ss, s); TEST_EQUAL(s.rules.size(), (size_t)68); TEST_EQUAL(to_string(s), script_text); elizalogic::tagmap tags(elizalogic::collect_tags(s.rules)); TEST_EQUAL(tags.size(), (size_t)3); TEST_EQUAL(join(tags["BELIEF"]), "BELIEVE FEEL THINK WISH"); TEST_EQUAL(join(tags["FAMILY"]), "BROTHER CHILDREN DAD FATHER MOM MOTHER SISTER WIFE"); TEST_EQUAL(join(tags["NOUN"]), "FATHER MOTHER"); struct exchange { const char * prompt; // input to ELIZA const char * response; // output expected from ELIZA }; const exchange conversation[] = { // --- exact conversation quoted in 1966 CACM article --- { "Men are all alike.", "IN WHAT WAY" }, { "They're always bugging us about something or other.", "CAN YOU THINK OF A SPECIFIC EXAMPLE" }, { "Well, my boyfriend made me come here.", "YOUR BOYFRIEND MADE YOU COME HERE" }, { "He says I'm depressed much of the time.", "I AM SORRY TO HEAR YOU ARE DEPRESSED" }, { "It's true. I am unhappy.", "DO YOU THINK COMING HERE WILL HELP YOU NOT TO BE UNHAPPY" }, { "I need some help, that much seems certain.", "WHAT WOULD IT MEAN TO YOU IF YOU GOT SOME HELP" }, { "Perhaps I could learn to get along with my mother.", "TELL ME MORE ABOUT YOUR FAMILY" }, { "My mother takes care of me.", "WHO ELSE IN YOUR FAMILY TAKES CARE OF YOU" }, { "My father.", "YOUR FATHER" }, { "You are like my father in some ways.", "WHAT RESEMBLANCE DO YOU SEE" }, { "You are not very aggressive but I think you don't want me to notice that.", "WHAT MAKES YOU THINK I AM NOT VERY AGGRESSIVE" }, { "You don't argue with me.", "WHY DO YOU THINK I DON'T ARGUE WITH YOU" }, { "You are afraid of me.", "DOES IT PLEASE YOU TO BELIEVE I AM AFRAID OF YOU" }, { "My father is afraid of everybody.", "WHAT ELSE COMES TO MIND WHEN YOU THINK OF YOUR FATHER" }, { "Bullies.", "DOES THAT HAVE ANYTHING TO DO WITH THE FACT THAT YOUR BOYFRIEND MADE YOU COME HERE" }, // --- end of 1966 CACM article conversation --- }; /* I believe that if this code reproduces the exact conversation published in the CACM article, using the mechanisms described in that article, it is not unreasonable to suppose it is a fairly accurate simulation of the original ELIZA. */ elizalogic::eliza eliza(s.rules); for (const auto & exchg : conversation) TEST_EQUAL(eliza.response(exchg.prompt), exchg.response); } }//namespace elizatest // write given s to std::cout, followed by newline void writeln(const std::string & s) { if (false) { // for fun, output 's' as if ELIZA was running on a 1966 Teletype auto sleep = [](long ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); }; using std::cout; using std::flush; using std::endl; for (const auto c : s) { cout << c << flush; sleep(100); } cout << endl; } else { // spit out all of 's' in an instant std::cout << s << std::endl; } } #if defined(_WIN32) const std::string option_escape("/"); #else const std::string option_escape("--"); #endif bool is_option(const std::string s) { return s.compare(0, option_escape.size(), option_escape) == 0; } bool is_option(const std::string s, const std::string opt) { return s.size() == option_escape.size() + opt.size() && s.compare(0, option_escape.size(), option_escape) == 0 && s.compare(option_escape.size(), opt.size(), opt) == 0; } std::string as_option(std::string o) { return option_escape + o; } int main(int argc, const char * argv[]) { try { std::cout << "-----------------------------------------------------------------\n" " ELIZA -- A Computer Program for the Study of Natural\n" " Language Communication Between Man and Machine\n" " DOCTOR script (c) Joseph Weizenbaum, 1966\n" "This implementation by Anthony Hay, 2022 (CC0 1.0) Public Domain\n" "-----------------------------------------------------------------\n"; RUN_TESTS(); // run the tests defined with DEF_TEST_FUNC elizascript::script s; if (argc == 1) { // use default 'internal' 1966 CACM published script std::cout << "ELIZA " << as_option("help") << " for usage.\n" << "Using Weizenbaum's 1966 DOCTOR script.\n" << "Enter a blank line to quit." << "\n\n\n"; std::stringstream ss(elizascript::CACM_1966_01_DOCTOR_script); elizascript::read<std::stringstream>(ss, s); } else if (argc == 2 && as_option("showscript") == argv[1]) { // just output Weizenbaum's DOCTOR script std::cout << elizascript::CACM_1966_01_DOCTOR_script; return EXIT_SUCCESS; } else if (argc == 2 && !is_option(argv[1])) { // use the named script file std::ifstream script_file(argv[1]); if (!script_file.is_open()) { std::cerr << argv[0] << ": failed to open script file '" << argv[1] << "'\n"; return EXIT_FAILURE; } std::cout << "Using given script file '" << argv[1] << "'\n\n\n"; elizascript::read<std::ifstream>(script_file, s); } else { std::cerr << "Usage: ELIZA [" << as_option("showscript") << " | <filename>]\n" << " where\n" << " " << as_option("showscript") << " dump Weizenbaum's 1966 DOCTOR script to stcout\n" << " e.g. ELIZA " << as_option("showscript") << " > script.txt\n" << " <filename> use named script file\n" << " e.g. ELIZA script.txt\n"; return EXIT_FAILURE; } writeln(join(s.hello_message)); for (elizalogic::eliza eliza(std::move(s.rules));;) { std::cout << std::endl; std::string userinput; std::getline(std::cin, userinput); if (userinput.empty()) break; writeln(eliza.response(userinput)); } } catch (const std::exception & e) { std::cerr << "exception: " << e.what() << std::endl; return EXIT_FAILURE; } catch (...) { std::cerr << "exception" << std::endl; return EXIT_FAILURE; } } // (The goal was to make only a minimum viable accurate simulation // of the original 1966 ELIZA rather than a polished product.)

Compiling Program...

Command line arguments:
Standard Input: Interactive Console Text
×

                

                

Program is not being debugged. Click "Debug" button to start program in debug mode.

#FunctionFile:Line
VariableValue
RegisterValue
ExpressionValue