+static void Output(bool syntax, CYUTF8String json, std::ostream *out, bool expand = false) {
+ const char *data(json.data);
+ size_t size(json.size);
+
+ if (data == NULL || out == NULL)
+ return;
+
+ if (!expand ||
+ data[0] != '@' && data[0] != '"' && data[0] != '\'' ||
+ data[0] == '@' && data[1] != '"' && data[1] != '\''
+ )
+ Write(syntax, data, size, *out);
+ else for (size_t i(0); i != size; ++i)
+ if (data[i] != '\\')
+ *out << data[i];
+ else switch(data[++i]) {
+ case '\0': goto done;
+ case '\\': *out << '\\'; break;
+ case '\'': *out << '\''; break;
+ case '"': *out << '"'; break;
+ case 'b': *out << '\b'; break;
+ case 'f': *out << '\f'; break;
+ case 'n': *out << '\n'; break;
+ case 'r': *out << '\r'; break;
+ case 't': *out << '\t'; break;
+ case 'v': *out << '\v'; break;
+ default: *out << '\\'; --i; break;
+ }
+
+ done:
+ *out << std::endl;
+}
+
+static void Run(int client, bool syntax, const char *data, size_t size, std::ostream *out = NULL, bool expand = false) {
+ CYPool pool;
+ Output(syntax, Run(pool, client, CYUTF8String(data, size)), out, expand);
+}
+
+static void Run(int client, bool syntax, std::string &code, std::ostream *out = NULL, bool expand = false) {
+ Run(client, syntax, code.c_str(), code.size(), out, expand);
+}
+
+int (*append_history$)(int, const char *);
+
+static std::string command_;
+
+static CYExpression *ParseExpression(CYUTF8String code) {
+ std::stringstream stream;
+ stream << '(' << code << ')';
+ CYDriver driver(stream);
+
+ cy::parser parser(driver);
+ Setup(driver, parser);
+
+ if (parser.parse() != 0 || !driver.errors_.empty())
+ return NULL;
+
+ CYOptions options;
+ CYContext context(options);
+
+ // XXX: this could be replaced with a CYStatement::Primitive()
+ if (CYExpress *express = dynamic_cast<CYExpress *>(driver.program_->statements_))
+ return express->expression_->Primitive(context);
+
+ return NULL;
+}
+
+static int client_;
+
+static char **Complete(const char *word, int start, int end) {
+ rl_attempted_completion_over = TRUE;
+
+ CYLocalPool pool;
+
+ std::string line(rl_line_buffer, start);
+ std::istringstream stream(command_ + line);
+ CYDriver driver(stream);
+
+ driver.auto_ = true;
+
+ cy::parser parser(driver);
+ Setup(driver, parser);
+
+ if (parser.parse() != 0 || !driver.errors_.empty())
+ return NULL;
+
+ if (driver.mode_ == CYDriver::AutoNone)
+ return NULL;
+
+ CYExpression *expression;
+
+ CYOptions options;
+ CYContext context(options);
+
+ std::ostringstream prefix;
+
+ switch (driver.mode_) {
+ case CYDriver::AutoPrimary:
+ expression = $ CYThis();
+ break;
+
+ case CYDriver::AutoDirect:
+ expression = driver.context_;
+ break;
+
+ case CYDriver::AutoIndirect:
+ expression = $ CYIndirect(driver.context_);
+ break;
+
+ case CYDriver::AutoMessage: {
+ CYDriver::Context &thing(driver.contexts_.back());
+ expression = $M($C1($V("object_getClass"), thing.context_), $S("messages"));
+ for (CYDriver::Context::Words::const_iterator part(thing.words_.begin()); part != thing.words_.end(); ++part)
+ prefix << (*part)->word_ << ':';
+ } break;
+
+ default:
+ _assert(false);
+ }
+
+ std::string begin(prefix.str());
+
+ driver.program_ = $ CYProgram($ CYExpress($C3(ParseExpression(
+ " function(object, prefix, word) {\n"
+ " var names = [];\n"
+ " var before = prefix.length;\n"
+ " prefix += word;\n"
+ " var entire = prefix.length;\n"
+ " for (name in object)\n"
+ " if (name.substring(0, entire) == prefix)\n"
+ " names.push(name.substr(before));\n"
+ " return names;\n"
+ " }\n"
+ ), expression, $S(begin.c_str()), $S(word))));
+
+ driver.program_->Replace(context);
+
+ std::ostringstream str;
+ CYOutput out(str, options);
+ out << *driver.program_;
+
+ std::string code(str.str());
+ CYUTF8String json(Run(pool, client_, code));
+ // XXX: if this fails we should not try to parse it
+
+ CYExpression *result(ParseExpression(json));
+ if (result == NULL)
+ return NULL;
+
+ CYArray *array(dynamic_cast<CYArray *>(result));
+
+ if (array == NULL) {
+ *out_ << '\n';
+ Output(false, json, out_);
+ rl_forced_update_display();
+ return NULL;
+ }
+
+ // XXX: use an std::set?
+ typedef std::vector<std::string> Completions;
+ Completions completions;
+
+ std::string common;
+ bool rest(false);
+
+ CYForEach (element, array->elements_) {
+ CYString *string(dynamic_cast<CYString *>(element->value_));
+ _assert(string != NULL);
+
+ std::string completion;
+ if (string->size_ != 0)
+ completion.assign(string->value_, string->size_);
+ else if (driver.mode_ == CYDriver::AutoMessage)
+ completion = "]";
+ else
+ continue;
+
+ completions.push_back(completion);
+
+ if (!rest) {
+ common = completion;
+ rest = true;
+ } else {
+ size_t limit(completion.size()), size(common.size());
+ if (size > limit)
+ common = common.substr(0, limit);
+ else
+ limit = size;
+ for (limit = 0; limit != size; ++limit)
+ if (common[limit] != completion[limit])
+ break;
+ if (limit != size)
+ common = common.substr(0, limit);
+ }
+ }
+
+ size_t count(completions.size());
+ if (count == 0)
+ return NULL;
+
+ size_t colon(common.find(':'));
+ if (colon != std::string::npos)
+ common = common.substr(0, colon + 1);
+ if (completions.size() == 1)
+ common += ' ';
+
+ char **results(reinterpret_cast<char **>(malloc(sizeof(char *) * (count + 2))));
+
+ results[0] = strdup(common.c_str());
+ size_t index(0);
+ for (Completions::const_iterator i(completions.begin()); i != completions.end(); ++i)
+ results[++index] = strdup(i->c_str());
+ results[count + 1] = NULL;
+
+ return results;
+}
+
+// need char *, not const char *
+static char name_[] = "cycript";
+static char break_[] = " \t\n\"\\'`@$><=;|&{(" ")}" ".:[]";
+
+static void Console(CYOptions &options) {
+ CYPool pool;
+
+ passwd *passwd;
+ if (const char *username = getenv("LOGNAME"))
+ passwd = getpwnam(username);
+ else
+ passwd = getpwuid(getuid());
+
+ const char *basedir(pool.strcat(passwd->pw_dir, "/.cycript", NULL));
+ const char *histfile(pool.strcat(basedir, "/history", NULL));
+ size_t histlines(0);
+
+ rl_initialize();
+ rl_readline_name = name_;
+
+ mkdir(basedir, 0700);
+ read_history(histfile);
+