From 7e5391fd937dd287b3aef363e641f2d3abc5f422 Mon Sep 17 00:00:00 2001 From: "Jay Freeman (saurik)" Date: Tue, 27 Apr 2010 00:32:19 +0000 Subject: [PATCH] Tab-complete FTW! --- Console.cpp | 252 ++++++++++++++++++++++++++++++++++++++++++-------- Cycript.l.in | 3 +- Cycript.yy.in | 29 ++++-- Handler.mm | 2 +- Library.cpp | 2 +- Parser.cpp | 8 +- Parser.hpp | 29 +++++- Pooling.hpp | 4 +- String.hpp | 13 +++ todo.txt | 1 + 10 files changed, 290 insertions(+), 53 deletions(-) diff --git a/Console.cpp b/Console.cpp index 8e1c0da..4a27c64 100644 --- a/Console.cpp +++ b/Console.cpp @@ -81,6 +81,8 @@ #include +#include "Replace.hpp" + static volatile enum { Working, Parsing, @@ -127,14 +129,14 @@ void Setup(CYOutput &out, CYDriver &driver, CYOptions &options) { driver.program_->Replace(context); } -void Run(int client, const char *data, size_t size, FILE *fout = NULL, bool expand = false) { - CYPool pool; - +static CYUTF8String Run(CYPool &pool, int client, CYUTF8String code) { const char *json; + size_t size; + if (client == -1) { mode_ = Running; #ifdef CY_EXECUTE - json = CYExecute(pool, data); + json = CYExecute(pool, code.data); #else json = NULL; #endif @@ -144,7 +146,7 @@ void Run(int client, const char *data, size_t size, FILE *fout = NULL, bool expa } else { mode_ = Sending; CYSendAll(client, &size, sizeof(size)); - CYSendAll(client, data, size); + CYSendAll(client, code.data, size); mode_ = Waiting; CYRecvAll(client, &size, sizeof(size)); if (size == _not(size_t)) @@ -158,13 +160,27 @@ void Run(int client, const char *data, size_t size, FILE *fout = NULL, bool expa mode_ = Working; } - if (json != NULL && fout != NULL) { - if (!expand || json[0] != '"' && json[0] != '\'') - fputs(json, fout); + return CYUTF8String(json, size); +} + +static CYUTF8String Run(CYPool &pool, int client, const std::string &code) { + return Run(pool, client, CYUTF8String(code.c_str(), code.size())); +} + +void Run(int client, const char *data, size_t size, FILE *fout = NULL, bool expand = false) { + CYPool pool; + CYUTF8String json(Run(pool, client, CYUTF8String(data, size))); + + data = json.data; + size = json.size; + + if (data != NULL && fout != NULL) { + if (!expand || data[0] != '"' && data[0] != '\'') + fputs(data, fout); else for (size_t i(0); i != size; ++i) - if (json[i] != '\\') - fputc(json[i], fout); - else switch(json[++i]) { + if (data[i] != '\\') + fputc(data[i], fout); + else switch(data[++i]) { case '\0': goto done; case '\\': fputc('\\', fout); break; case '\'': fputc('\'', fout); break; @@ -184,13 +200,173 @@ void Run(int client, const char *data, size_t size, FILE *fout = NULL, bool expa } } -void Run(int client, std::string &code, FILE *fout = NULL, bool expand = false) { +static void Run(int client, std::string &code, FILE *fout = NULL, bool expand = false) { Run(client, code.c_str(), code.size(), fout, expand); } int (*append_history$)(int, const char *); -static void Console(apr_pool_t *pool, int client, CYOptions &options) { +static std::string command_; + +static CYExpression *ParseExpression(CYPool &pool, CYUTF8String code) { + std::ostringstream str; + str << '(' << code << ')'; + std::string string(str.str()); + + CYDriver driver(pool); + driver.data_ = string.c_str(); + driver.size_ = string.size(); + + cy::parser parser(driver); + Setup(driver, parser); + + if (parser.parse() != 0 || !driver.errors_.empty()) + _assert(false); + + CYExpress *express(dynamic_cast(driver.program_->statements_)); + _assert(express != NULL); + return express->expression_; +} + +static int client_; + +static char **Complete(const char *word, int start, int end) { + rl_attempted_completion_over = TRUE; + + CYPool pool; + + CYDriver driver(pool); + cy::parser parser(driver); + Setup(driver, parser); + + std::string line(rl_line_buffer, start); + std::string command(command_ + line); + + driver.data_ = command.c_str(); + driver.size_ = command.size(); + + driver.auto_ = true; + + if (parser.parse() != 0 || !driver.errors_.empty()) + return NULL; + + if (driver.mode_ == CYDriver::AutoNone) + return NULL; + + CYExpression *expression; + + CYOptions options; + CYContext context(driver.pool_, 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($M($ CYIndirect(thing.context_), $S("isa")), $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() + word); + + driver.program_ = $ CYProgram($ CYExpress($C2(ParseExpression(pool, + " function(object, prefix) {\n" + " var names = [];\n" + " var pattern = '^' + prefix;\n" + " for (name in object)\n" + " if (name.match(pattern) != null)\n" + " names.push(name);\n" + " return names;\n" + " }\n" + ), expression, $S(begin.c_str())))); + + 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)); + + CYExpression *result(ParseExpression(pool, json)); + CYArray *array(dynamic_cast(result)); + _assert(array != NULL); + + // XXX: use an std::set? + typedef std::vector Completions; + Completions completions; + + std::string common; + bool rest(false); + + for (CYElement *element(array->elements_); element != NULL; element = element->next_) { + CYString *string(dynamic_cast(element->value_)); + _assert(string != NULL); + + std::string completion(string->value_, string->size_); + 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; + + if (!common.empty()) { + size_t size(prefix.str().size()); + _assert(common.size() >= size); + common = common.substr(size); + } + + char **results(reinterpret_cast(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(apr_pool_t *pool, CYOptions &options) { passwd *passwd; if (const char *username = getenv("LOGNAME")) passwd = getpwnam(username); @@ -201,6 +377,9 @@ static void Console(apr_pool_t *pool, int client, CYOptions &options) { const char *histfile(apr_psprintf(pool, "%s/history", basedir)); size_t histlines(0); + rl_initialize(); + rl_readline_name = name_; + mkdir(basedir, 0700); read_history(histfile); @@ -210,7 +389,10 @@ static void Console(apr_pool_t *pool, int client, CYOptions &options) { FILE *fout(stdout); - rl_bind_key('\t', rl_insert); + // rl_completer_word_break_characters is broken in libedit + rl_basic_word_break_characters = break_; + rl_attempted_completion_function = &Complete; + rl_bind_key('\t', rl_complete); struct sigaction action; sigemptyset(&action.sa_mask); @@ -219,7 +401,7 @@ static void Console(apr_pool_t *pool, int client, CYOptions &options) { sigaction(SIGINT, &action, NULL); restart: for (;;) { - std::string command; + command_.clear(); std::vector lines; bool extra(false); @@ -264,7 +446,7 @@ static void Console(apr_pool_t *pool, int client, CYOptions &options) { } } - command += line; + command_ += line; char *begin(line), *end(line + strlen(line)); while (char *nl = reinterpret_cast(memchr(begin, '\n', end - begin))) { @@ -280,14 +462,14 @@ static void Console(apr_pool_t *pool, int client, CYOptions &options) { std::string code; if (bypass) - code = command; + code = command_; else { - CYDriver driver(""); + CYDriver driver; cy::parser parser(driver); Setup(driver, parser); - driver.data_ = command.c_str(); - driver.size_ = command.size(); + driver.data_ = command_.c_str(); + driver.size_ = command_.size(); if (parser.parse() != 0 || !driver.errors_.empty()) { for (CYDriver::Errors::const_iterator error(driver.errors_.begin()); error != driver.errors_.end(); ++error) { @@ -312,7 +494,7 @@ static void Console(apr_pool_t *pool, int client, CYOptions &options) { std::cerr << " | "; std::cerr << error->message_ << std::endl; - add_history(command.c_str()); + add_history(command_.c_str()); ++histlines; goto restart; } @@ -320,7 +502,7 @@ static void Console(apr_pool_t *pool, int client, CYOptions &options) { driver.errors_.clear(); - command += '\n'; + command_ += '\n'; prompt = "cy> "; goto read; } @@ -328,8 +510,8 @@ static void Console(apr_pool_t *pool, int client, CYOptions &options) { if (driver.program_ == NULL) goto restart; - if (client != -1) - code = command; + if (client_ != -1) + code = command_; else { std::ostringstream str; CYOutput out(str, options); @@ -339,13 +521,13 @@ static void Console(apr_pool_t *pool, int client, CYOptions &options) { } } - add_history(command.c_str()); + add_history(command_.c_str()); ++histlines; if (debug) std::cout << code << std::endl; - Run(client, code, fout, expand); + Run(client_, code, fout, expand); } if (append_history$ != NULL) { @@ -542,11 +724,9 @@ int Main(int argc, char const * const argv[], char const * const envp[]) { } #endif - int client; - #ifdef CY_ATTACH if (pid == _not(pid_t)) - client = -1; + client_ = -1; else { int server(_syscall(socket(PF_UNIX, SOCK_STREAM, 0))); try { struct sockaddr_un address; @@ -561,7 +741,7 @@ int Main(int argc, char const * const argv[], char const * const envp[]) { try { _syscall(listen(server, 1)); InjectLibrary(pid); - client = _syscall(accept(server, NULL, NULL)); + client_ = _syscall(accept(server, NULL, NULL)); } catch (...) { // XXX: exception? unlink(address.sun_path); @@ -573,13 +753,13 @@ int Main(int argc, char const * const argv[], char const * const envp[]) { } } #else - client = -1; + client_ = -1; #endif if (script == NULL && tty) - Console(pool, client, options); + Console(pool, options); else { - CYDriver driver(script ?: ""); + CYDriver driver(pool, script ?: ""); cy::parser parser(driver); Setup(driver, parser); @@ -612,9 +792,9 @@ int Main(int argc, char const * const argv[], char const * const envp[]) { for (CYDriver::Errors::const_iterator i(driver.errors_.begin()); i != driver.errors_.end(); ++i) std::cerr << i->location_.begin << ": " << i->message_ << std::endl; } else if (driver.program_ != NULL) - if (client != -1) { + if (client_ != -1) { std::string code(start, end-start); - Run(client, code, stdout); + Run(client_, code, stdout); } else { std::ostringstream str; CYOutput out(str, options); @@ -624,7 +804,7 @@ int Main(int argc, char const * const argv[], char const * const envp[]) { if (compile) std::cout << code; else - Run(client, code, stdout); + Run(client_, code, stdout); } } diff --git a/Cycript.l.in b/Cycript.l.in index e085233..9e984b1 100644 --- a/Cycript.l.in +++ b/Cycript.l.in @@ -384,7 +384,8 @@ XMLName {XMLNameStart}{XMLNamePart}* \r?\n yylloc->end.lines(); yylloc->step(); N [ \t] L -<> L yyterminate(); + +<> if (yyextra->auto_) { yyextra->auto_ = false; return tk::AutoComplete_; } L yyterminate(); . L { CYDriver::Error error; diff --git a/Cycript.yy.in b/Cycript.yy.in index 0181676..ecfbba1 100644 --- a/Cycript.yy.in +++ b/Cycript.yy.in @@ -317,6 +317,8 @@ int cylex(YYSTYPE *, cy::location *, void *); %token XML "xml" @end +%token AutoComplete + %token Identifier_ %token NumericLiteral %token StringLiteral @@ -503,6 +505,7 @@ int cylex(YYSTYPE *, cy::location *, void *); %type SelectorExpression_ %type SelectorExpressionOpt %type SelectorList +%type SelectorWordOpt %type TypeOpt %type VariadicCall %type Word @@ -719,6 +722,7 @@ PrimaryExpressionNoRE PrimaryExpressionNo : "this" { $$ = $1; } | Identifier { $$ = new(driver.pool_) CYVariable($1); } + | AutoComplete { driver.mode_ = CYDriver::AutoPrimary; YYACCEPT; } | Literal { $$ = $1; } | ArrayLiteral { $$ = $1; } | "(" Expression ")" { $$ = $2; } @@ -790,18 +794,19 @@ MemberExpression_ MemberAccess : "[" Expression "]" { $$ = new(driver.pool_) CYDirectMember(NULL, $2); } | "." Identifier { $$ = new(driver.pool_) CYDirectMember(NULL, new(driver.pool_) CYString($2)); } + | "." AutoComplete { driver.mode_ = CYDriver::AutoDirect; YYACCEPT; } ; MemberExpression : PrimaryExpression { $$ = $1; } | LexSetRegExp FunctionExpression { $$ = $2; } - | MemberExpression MemberAccess { $2->SetLeft($1); $$ = $2; } + | MemberExpression { driver.context_ = $1; } MemberAccess { $3->SetLeft($1); $$ = $3; } | LexSetRegExp MemberExpression_ { $$ = $2; } ; MemberExpressionNoBF : PrimaryExpressionNoBF { $$ = $1; } - | MemberExpressionNoBF MemberAccess { $2->SetLeft($1); $$ = $2; } + | MemberExpressionNoBF { driver.context_ = $1; } MemberAccess { $3->SetLeft($1); $$ = $3; } | MemberExpression_ { $$ = $1; } ; @@ -809,7 +814,7 @@ MemberExpressionNoBF MemberExpressionNoRE : PrimaryExpressionNoRE { $$ = $1; } | FunctionExpression { $$ = $1; } - | MemberExpressionNoRE MemberAccess { $2->SetLeft($1); $$ = $2; } + | MemberExpressionNoRE { driver.context_ = $1; } MemberAccess { $3->SetLeft($1); $$ = $3; } | MemberExpression_ { $$ = $1; } ; @end @@ -838,20 +843,20 @@ NewExpressionNoRE CallExpression : MemberExpression Arguments { $$ = new(driver.pool_) CYCall($1, $2); } | CallExpression Arguments { $$ = new(driver.pool_) CYCall($1, $2); } - | CallExpression MemberAccess { $2->SetLeft($1); $$ = $2; } + | CallExpression { driver.context_ = $1; } MemberAccess { $3->SetLeft($1); $$ = $3; } ; CallExpressionNoBF : MemberExpressionNoBF Arguments { $$ = new(driver.pool_) CYCall($1, $2); } | CallExpressionNoBF Arguments { $$ = new(driver.pool_) CYCall($1, $2); } - | CallExpressionNoBF MemberAccess { $2->SetLeft($1); $$ = $2; } + | CallExpressionNoBF { driver.context_ = $1; } MemberAccess { $3->SetLeft($1); $$ = $3; } ; @begin C CallExpressionNoRE : MemberExpressionNoRE Arguments { $$ = new(driver.pool_) CYCall($1, $2); } | CallExpressionNoRE Arguments { $$ = new(driver.pool_) CYCall($1, $2); } - | CallExpressionNoRE MemberAccess { $2->SetLeft($1); $$ = $2; } + | CallExpressionNoRE { driver.context_ = $1; } MemberAccess { $3->SetLeft($1); $$ = $3; } ; @end @@ -1598,8 +1603,13 @@ SelectorCall_ | VariadicCall { $$ = $1; } ; +SelectorWordOpt + : WordOpt { driver.contexts_.back().words_.push_back($1); } { $$ = $1; } + | AutoComplete { driver.mode_ = CYDriver::AutoMessage; YYACCEPT; } + ; + SelectorCall - : WordOpt ":" AssignmentExpression SelectorCall_ { $$ = new(driver.pool_) CYArgument($1 ?: new(driver.pool_) CYBlank(), $3, $4); } + : SelectorWordOpt ":" AssignmentExpression SelectorCall_ { $$ = new(driver.pool_) CYArgument($1 ?: new(driver.pool_) CYBlank(), $3, $4); } ; SelectorList @@ -1608,8 +1618,8 @@ SelectorList ; MessageExpression - : "[" AssignmentExpression SelectorList "]" { $$ = new(driver.pool_) CYSendDirect($2, $3); } - | "[" LexSetRegExp "super" SelectorList "]" { $$ = new(driver.pool_) CYSendSuper($4); } + : "[" AssignmentExpression { driver.contexts_.push_back($2); } SelectorList "]" { driver.contexts_.pop_back(); } { $$ = new(driver.pool_) CYSendDirect($2, $4); } + | "[" LexSetRegExp "super" { driver.context_ = NULL; } SelectorList "]" { $$ = new(driver.pool_) CYSendSuper($5); } ; SelectorExpressionOpt @@ -1646,6 +1656,7 @@ UnaryExpression_ MemberAccess : "->" "[" Expression "]" { $$ = new(driver.pool_) CYIndirectMember(NULL, $3); } | "->" Identifier { $$ = new(driver.pool_) CYIndirectMember(NULL, new(driver.pool_) CYString($2)); } + | "->" AutoComplete { driver.mode_ = CYDriver::AutoIndirect; YYACCEPT; } ; /* }}} */ @end diff --git a/Handler.mm b/Handler.mm index 4e4ccfe..833ebf3 100644 --- a/Handler.mm +++ b/Handler.mm @@ -117,7 +117,7 @@ struct CYClient : return; data[size] = '\0'; - CYDriver driver(""); + CYDriver driver; cy::parser parser(driver); driver.data_ = data; diff --git a/Library.cpp b/Library.cpp index 2b85f96..d375d10 100644 --- a/Library.cpp +++ b/Library.cpp @@ -241,7 +241,7 @@ double CYCastDouble(const char *value) { } extern "C" void CydgetPoolParse(apr_pool_t *pool, const uint16_t **data, size_t *size) { - CYDriver driver(""); + CYDriver driver; cy::parser parser(driver); CYUTF8String utf8(CYPoolUTF8String(pool, CYUTF16String(*data, *size))); diff --git a/Parser.cpp b/Parser.cpp index 3f80b20..49f2d10 100644 --- a/Parser.cpp +++ b/Parser.cpp @@ -44,14 +44,18 @@ CYRange DigitRange_ (0x3ff000000000000LLU, 0x000000000000000LLU); // 0-9 CYRange WordStartRange_(0x000001000000000LLU, 0x7fffffe87fffffeLLU); // A-Za-z_$ CYRange WordEndRange_ (0x3ff001000000000LLU, 0x7fffffe87fffffeLLU); // A-Za-z_$0-9 -CYDriver::CYDriver(const std::string &filename) : +CYDriver::CYDriver(apr_pool_t *pool, const std::string &filename) : + pool_(pool), state_(CYClear), data_(NULL), size_(0), file_(NULL), strict_(false), filename_(filename), - program_(NULL) + program_(NULL), + auto_(false), + context_(NULL), + mode_(AutoNone) { ScannerInit(); } diff --git a/Parser.hpp b/Parser.hpp index f48e81b..a994ead 100644 --- a/Parser.hpp +++ b/Parser.hpp @@ -453,12 +453,39 @@ class CYDriver { CYProgram *program_; Errors errors_; + bool auto_; + + struct Context { + CYExpression *context_; + + Context(CYExpression *context) : + context_(context) + { + } + + typedef std::vector Words; + Words words_; + }; + + typedef std::vector Contexts; + Contexts contexts_; + + CYExpression *context_; + + enum Mode { + AutoNone, + AutoPrimary, + AutoDirect, + AutoIndirect, + AutoMessage + } mode_; + private: void ScannerInit(); void ScannerDestroy(); public: - CYDriver(const std::string &filename); + CYDriver(apr_pool_t *pool = NULL, const std::string &filename = ""); ~CYDriver(); Condition GetCondition(); diff --git a/Pooling.hpp b/Pooling.hpp index a182906..d1ee2b7 100644 --- a/Pooling.hpp +++ b/Pooling.hpp @@ -61,8 +61,8 @@ class CYPool { apr_pool_t *pool_; public: - CYPool() { - _aprcall(apr_pool_create(&pool_, NULL)); + CYPool(apr_pool_t *pool = NULL) { + _aprcall(apr_pool_create(&pool_, pool)); } ~CYPool() { diff --git a/String.hpp b/String.hpp index 754cdbc..ec45c47 100644 --- a/String.hpp +++ b/String.hpp @@ -43,10 +43,18 @@ #include "cycript.hpp" #include "Pooling.hpp" +#include + struct CYUTF8String { const char *data; size_t size; + CYUTF8String(const char *data) : + data(data), + size(strlen(data)) + { + } + CYUTF8String(const char *data, size_t size) : data(data), size(size) @@ -59,6 +67,11 @@ struct CYUTF8String { } }; +static inline std::ostream &operator <<(std::ostream &lhs, CYUTF8String &rhs) { + lhs.write(rhs.data, rhs.size); + return lhs; +} + struct CYUTF16String { const uint16_t *data; size_t size; diff --git a/todo.txt b/todo.txt index bc4bea2..e5690de 100644 --- a/todo.txt +++ b/todo.txt @@ -27,3 +27,4 @@ applyOnMainThread, when done at console, loops the cyonifier special work needs to be done to correctly handle the "arguments" symbol: Declare("arguments", ...Special) at the Program level I seem to be eating away all of the var statements function pointers are ?; note that blocks are currently block_P = '?' +I should probably attempt to use the auto_ flag somehow to not do contexts_ push when compiling -- 2.45.2