]> git.saurik.com Git - cycript.git/blob - Console.cpp
934ab8c618a07a71f5a0b46f734ff135e90d7b72
[cycript.git] / Console.cpp
1 /* Cycript - Optimizing JavaScript Compiler/Runtime
2 * Copyright (C) 2009-2012 Jay Freeman (saurik)
3 */
4
5 /* GNU Lesser General Public License, Version 3 {{{ */
6 /*
7 * Cycript is free software: you can redistribute it and/or modify it under
8 * the terms of the GNU Lesser General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * Cycript is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
15 * License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with Cycript. If not, see <http://www.gnu.org/licenses/>.
19 **/
20 /* }}} */
21
22 #include "cycript.hpp"
23
24 #ifdef CY_EXECUTE
25 #include "JavaScript.hpp"
26 #endif
27
28 #include <cstdio>
29 #include <sstream>
30
31 #include <setjmp.h>
32
33 #ifdef HAVE_READLINE_H
34 #include <readline.h>
35 #else
36 #include <readline/readline.h>
37 #endif
38
39 #ifdef HAVE_HISTORY_H
40 #include <history.h>
41 #else
42 #include <readline/history.h>
43 #endif
44
45 #include <sys/mman.h>
46
47 #include <errno.h>
48 #include <unistd.h>
49
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <fcntl.h>
53
54 #include "Cycript.tab.hh"
55
56 #include <sys/types.h>
57 #include <sys/socket.h>
58 #include <netinet/in.h>
59 #include <sys/un.h>
60 #include <pwd.h>
61
62 #include <apr_getopt.h>
63
64 #include <dlfcn.h>
65
66 #include "Replace.hpp"
67
68 static volatile enum {
69 Working,
70 Parsing,
71 Running,
72 Sending,
73 Waiting,
74 } mode_;
75
76 static jmp_buf ctrlc_;
77
78 static void sigint(int) {
79 switch (mode_) {
80 case Working:
81 return;
82 case Parsing:
83 longjmp(ctrlc_, 1);
84 case Running:
85 throw "*** Ctrl-C";
86 case Sending:
87 return;
88 case Waiting:
89 return;
90 }
91 }
92
93 #if YYDEBUG
94 static bool bison_;
95 #endif
96 static bool strict_;
97 static bool pretty_;
98
99 void Setup(CYDriver &driver, cy::parser &parser) {
100 #if YYDEBUG
101 if (bison_)
102 parser.set_debug_level(1);
103 #endif
104 if (strict_)
105 driver.strict_ = true;
106 }
107
108 void Setup(CYOutput &out, CYDriver &driver, CYOptions &options) {
109 out.pretty_ = pretty_;
110 CYContext context(options);
111 driver.program_->Replace(context);
112 }
113
114 static CYUTF8String Run(CYPool &pool, int client, CYUTF8String code) {
115 const char *json;
116 size_t size;
117
118 if (client == -1) {
119 mode_ = Running;
120 #ifdef CY_EXECUTE
121 json = CYExecute(pool, code);
122 #else
123 json = NULL;
124 #endif
125 mode_ = Working;
126 if (json == NULL)
127 size = 0;
128 else
129 size = strlen(json);
130 } else {
131 mode_ = Sending;
132 size = code.size;
133 CYSendAll(client, &size, sizeof(size));
134 CYSendAll(client, code.data, code.size);
135 mode_ = Waiting;
136 CYRecvAll(client, &size, sizeof(size));
137 if (size == _not(size_t))
138 json = NULL;
139 else {
140 char *temp(new(pool) char[size + 1]);
141 CYRecvAll(client, temp, size);
142 temp[size] = '\0';
143 json = temp;
144 }
145 mode_ = Working;
146 }
147
148 return CYUTF8String(json, size);
149 }
150
151 static CYUTF8String Run(CYPool &pool, int client, const std::string &code) {
152 return Run(pool, client, CYUTF8String(code.c_str(), code.size()));
153 }
154
155 FILE *fout_;
156
157 static void Output(CYUTF8String json, FILE *fout, bool expand = false) {
158 const char *data(json.data);
159 size_t size(json.size);
160
161 if (data == NULL || fout == NULL)
162 return;
163
164 if (!expand ||
165 data[0] != '@' && data[0] != '"' && data[0] != '\'' ||
166 data[0] == '@' && data[1] != '"' && data[1] != '\''
167 )
168 fputs(data, fout);
169 else for (size_t i(0); i != size; ++i)
170 if (data[i] != '\\')
171 fputc(data[i], fout);
172 else switch(data[++i]) {
173 case '\0': goto done;
174 case '\\': fputc('\\', fout); break;
175 case '\'': fputc('\'', fout); break;
176 case '"': fputc('"', fout); break;
177 case 'b': fputc('\b', fout); break;
178 case 'f': fputc('\f', fout); break;
179 case 'n': fputc('\n', fout); break;
180 case 'r': fputc('\r', fout); break;
181 case 't': fputc('\t', fout); break;
182 case 'v': fputc('\v', fout); break;
183 default: fputc('\\', fout); --i; break;
184 }
185
186 done:
187 fputs("\n", fout);
188 fflush(fout);
189 }
190
191 static void Run(int client, const char *data, size_t size, FILE *fout = NULL, bool expand = false) {
192 CYPool pool;
193 Output(Run(pool, client, CYUTF8String(data, size)), fout, expand);
194 }
195
196 static void Run(int client, std::string &code, FILE *fout = NULL, bool expand = false) {
197 Run(client, code.c_str(), code.size(), fout, expand);
198 }
199
200 int (*append_history$)(int, const char *);
201
202 static std::string command_;
203
204 static CYExpression *ParseExpression(CYUTF8String code) {
205 std::ostringstream str;
206 str << '(' << code << ')';
207 std::string string(str.str());
208
209 CYDriver driver;
210 driver.data_ = string.c_str();
211 driver.size_ = string.size();
212
213 cy::parser parser(driver);
214 Setup(driver, parser);
215
216 if (parser.parse() != 0 || !driver.errors_.empty())
217 return NULL;
218
219 CYOptions options;
220 CYContext context(options);
221
222 // XXX: this could be replaced with a CYStatement::Primitive()
223 if (CYExpress *express = dynamic_cast<CYExpress *>(driver.program_->statements_))
224 return express->expression_->Primitive(context);
225
226 return NULL;
227 }
228
229 static int client_;
230
231 static char **Complete(const char *word, int start, int end) {
232 rl_attempted_completion_over = TRUE;
233
234 CYLocalPool pool;
235 CYDriver driver;
236 cy::parser parser(driver);
237 Setup(driver, parser);
238
239 std::string line(rl_line_buffer, start);
240 std::string command(command_ + line);
241
242 driver.data_ = command.c_str();
243 driver.size_ = command.size();
244
245 driver.auto_ = true;
246
247 if (parser.parse() != 0 || !driver.errors_.empty())
248 return NULL;
249
250 if (driver.mode_ == CYDriver::AutoNone)
251 return NULL;
252
253 CYExpression *expression;
254
255 CYOptions options;
256 CYContext context(options);
257
258 std::ostringstream prefix;
259
260 switch (driver.mode_) {
261 case CYDriver::AutoPrimary:
262 expression = $ CYThis();
263 break;
264
265 case CYDriver::AutoDirect:
266 expression = driver.context_;
267 break;
268
269 case CYDriver::AutoIndirect:
270 expression = $ CYIndirect(driver.context_);
271 break;
272
273 case CYDriver::AutoMessage: {
274 CYDriver::Context &thing(driver.contexts_.back());
275 expression = $M($C1($V("object_getClass"), thing.context_), $S("messages"));
276 for (CYDriver::Context::Words::const_iterator part(thing.words_.begin()); part != thing.words_.end(); ++part)
277 prefix << (*part)->word_ << ':';
278 } break;
279
280 default:
281 _assert(false);
282 }
283
284 std::string begin(prefix.str());
285
286 driver.program_ = $ CYProgram($ CYExpress($C3(ParseExpression(
287 " function(object, prefix, word) {\n"
288 " var names = [];\n"
289 " var before = prefix.length;\n"
290 " prefix += word;\n"
291 " var entire = prefix.length;\n"
292 " for (name in object)\n"
293 " if (name.substring(0, entire) == prefix)\n"
294 " names.push(name.substr(before));\n"
295 " return names;\n"
296 " }\n"
297 ), expression, $S(begin.c_str()), $S(word))));
298
299 driver.program_->Replace(context);
300
301 std::ostringstream str;
302 CYOutput out(str, options);
303 out << *driver.program_;
304
305 std::string code(str.str());
306 CYUTF8String json(Run(pool, client_, code));
307 // XXX: if this fails we should not try to parse it
308
309 CYExpression *result(ParseExpression(json));
310 if (result == NULL)
311 return NULL;
312
313 CYArray *array(dynamic_cast<CYArray *>(result));
314
315 if (array == NULL) {
316 fprintf(fout_, "\n");
317 Output(json, fout_);
318 rl_forced_update_display();
319 return NULL;
320 }
321
322 // XXX: use an std::set?
323 typedef std::vector<std::string> Completions;
324 Completions completions;
325
326 std::string common;
327 bool rest(false);
328
329 CYForEach (element, array->elements_) {
330 CYString *string(dynamic_cast<CYString *>(element->value_));
331 _assert(string != NULL);
332
333 std::string completion;
334 if (string->size_ != 0)
335 completion.assign(string->value_, string->size_);
336 else if (driver.mode_ == CYDriver::AutoMessage)
337 completion = "]";
338 else
339 continue;
340
341 completions.push_back(completion);
342
343 if (!rest) {
344 common = completion;
345 rest = true;
346 } else {
347 size_t limit(completion.size()), size(common.size());
348 if (size > limit)
349 common = common.substr(0, limit);
350 else
351 limit = size;
352 for (limit = 0; limit != size; ++limit)
353 if (common[limit] != completion[limit])
354 break;
355 if (limit != size)
356 common = common.substr(0, limit);
357 }
358 }
359
360 size_t count(completions.size());
361 if (count == 0)
362 return NULL;
363
364 size_t colon(common.find(':'));
365 if (colon != std::string::npos)
366 common = common.substr(0, colon + 1);
367 if (completions.size() == 1)
368 common += ' ';
369
370 char **results(reinterpret_cast<char **>(malloc(sizeof(char *) * (count + 2))));
371
372 results[0] = strdup(common.c_str());
373 size_t index(0);
374 for (Completions::const_iterator i(completions.begin()); i != completions.end(); ++i)
375 results[++index] = strdup(i->c_str());
376 results[count + 1] = NULL;
377
378 return results;
379 }
380
381 // need char *, not const char *
382 static char name_[] = "cycript";
383 static char break_[] = " \t\n\"\\'`@$><=;|&{(" ")}" ".:[]";
384
385 static void Console(CYOptions &options) {
386 CYPool pool;
387
388 passwd *passwd;
389 if (const char *username = getenv("LOGNAME"))
390 passwd = getpwnam(username);
391 else
392 passwd = getpwuid(getuid());
393
394 const char *basedir(apr_psprintf(pool, "%s/.cycript", passwd->pw_dir));
395 const char *histfile(apr_psprintf(pool, "%s/history", basedir));
396 size_t histlines(0);
397
398 rl_initialize();
399 rl_readline_name = name_;
400
401 mkdir(basedir, 0700);
402 read_history(histfile);
403
404 bool bypass(false);
405 bool debug(false);
406 bool expand(false);
407
408 fout_ = stdout;
409
410 // rl_completer_word_break_characters is broken in libedit
411 rl_basic_word_break_characters = break_;
412
413 rl_completer_word_break_characters = break_;
414 rl_attempted_completion_function = &Complete;
415 rl_bind_key('\t', rl_complete);
416
417 struct sigaction action;
418 sigemptyset(&action.sa_mask);
419 action.sa_handler = &sigint;
420 action.sa_flags = 0;
421 sigaction(SIGINT, &action, NULL);
422
423 restart: for (;;) {
424 command_.clear();
425 std::vector<std::string> lines;
426
427 bool extra(false);
428 const char *prompt("cy# ");
429
430 if (setjmp(ctrlc_) != 0) {
431 mode_ = Working;
432 fputs("\n", fout_);
433 fflush(fout_);
434 goto restart;
435 }
436
437 read:
438 mode_ = Parsing;
439 char *line(readline(prompt));
440 mode_ = Working;
441 if (line == NULL)
442 break;
443 if (line[0] == '\0')
444 goto read;
445
446 if (!extra) {
447 extra = true;
448 if (line[0] == '?') {
449 std::string data(line + 1);
450 if (data == "bypass") {
451 bypass = !bypass;
452 fprintf(fout_, "bypass == %s\n", bypass ? "true" : "false");
453 fflush(fout_);
454 } else if (data == "debug") {
455 debug = !debug;
456 fprintf(fout_, "debug == %s\n", debug ? "true" : "false");
457 fflush(fout_);
458 } else if (data == "expand") {
459 expand = !expand;
460 fprintf(fout_, "expand == %s\n", expand ? "true" : "false");
461 fflush(fout_);
462 }
463 add_history(line);
464 ++histlines;
465 goto restart;
466 }
467 }
468
469 command_ += line;
470
471 char *begin(line), *end(line + strlen(line));
472 while (char *nl = reinterpret_cast<char *>(memchr(begin, '\n', end - begin))) {
473 *nl = '\0';
474 lines.push_back(begin);
475 begin = nl + 1;
476 }
477
478 lines.push_back(begin);
479
480 free(line);
481
482 std::string code;
483
484 if (bypass)
485 code = command_;
486 else {
487 CYLocalPool pool;
488 CYDriver driver;
489 cy::parser parser(driver);
490 Setup(driver, parser);
491
492 driver.data_ = command_.c_str();
493 driver.size_ = command_.size();
494
495 if (parser.parse() != 0 || !driver.errors_.empty()) {
496 for (CYDriver::Errors::const_iterator error(driver.errors_.begin()); error != driver.errors_.end(); ++error) {
497 cy::position begin(error->location_.begin);
498 if (begin.line != lines.size() || begin.column < lines.back().size() || error->warning_) {
499 cy::position end(error->location_.end);
500
501 if (begin.line != lines.size()) {
502 std::cerr << " | ";
503 std::cerr << lines[begin.line - 1] << std::endl;
504 }
505
506 std::cerr << "....";
507 for (size_t i(0); i != begin.column; ++i)
508 std::cerr << '.';
509 if (begin.line != end.line || begin.column == end.column)
510 std::cerr << '^';
511 else for (size_t i(0), e(end.column - begin.column); i != e; ++i)
512 std::cerr << '^';
513 std::cerr << std::endl;
514
515 std::cerr << " | ";
516 std::cerr << error->message_ << std::endl;
517
518 add_history(command_.c_str());
519 ++histlines;
520 goto restart;
521 }
522 }
523
524 driver.errors_.clear();
525
526 command_ += '\n';
527 prompt = "cy> ";
528 goto read;
529 }
530
531 if (driver.program_ == NULL)
532 goto restart;
533
534 if (client_ != -1)
535 code = command_;
536 else {
537 std::ostringstream str;
538 CYOutput out(str, options);
539 Setup(out, driver, options);
540 out << *driver.program_;
541 code = str.str();
542 }
543 }
544
545 add_history(command_.c_str());
546 ++histlines;
547
548 if (debug)
549 std::cout << code << std::endl;
550
551 Run(client_, code, fout_, expand);
552 }
553
554 if (append_history$ != NULL) {
555 _syscall(close(_syscall(open(histfile, O_CREAT | O_WRONLY, 0600))));
556 (*append_history$)(histlines, histfile);
557 } else {
558 write_history(histfile);
559 }
560
561 fputs("\n", fout_);
562 fflush(fout_);
563 }
564
565 static void *Map(const char *path, size_t *psize) {
566 int fd;
567 _syscall(fd = open(path, O_RDONLY));
568
569 struct stat stat;
570 _syscall(fstat(fd, &stat));
571 size_t size(stat.st_size);
572
573 *psize = size;
574
575 void *base;
576 _syscall(base = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0));
577
578 _syscall(close(fd));
579 return base;
580 }
581
582 void InjectLibrary(pid_t pid);
583
584 int Main(int argc, char const * const argv[], char const * const envp[]) {
585 bool tty(isatty(STDIN_FILENO));
586 bool compile(false);
587 CYOptions options;
588
589 append_history$ = (int (*)(int, const char *)) (dlsym(RTLD_DEFAULT, "append_history"));
590
591 #ifdef CY_ATTACH
592 pid_t pid(_not(pid_t));
593 #endif
594
595 CYPool pool;
596 apr_getopt_t *state;
597 _aprcall(apr_getopt_init(&state, pool, argc, argv));
598
599 for (;;) {
600 char opt;
601 const char *arg;
602
603 apr_status_t status(apr_getopt(state,
604 "cg:n:"
605 #ifdef CY_ATTACH
606 "p:"
607 #endif
608 "s"
609 , &opt, &arg));
610
611 switch (status) {
612 case APR_EOF:
613 goto getopt;
614 case APR_BADCH:
615 case APR_BADARG:
616 fprintf(stderr,
617 "usage: cycript [-c]"
618 #ifdef CY_ATTACH
619 " [-p <pid|name>]"
620 #endif
621 " [<script> [<arg>...]]\n"
622 );
623 return 1;
624 default:
625 _aprcall(status);
626 }
627
628 switch (opt) {
629 case 'c':
630 compile = true;
631 break;
632
633 case 'g':
634 if (false);
635 else if (strcmp(arg, "rename") == 0)
636 options.verbose_ = true;
637 #if YYDEBUG
638 else if (strcmp(arg, "bison") == 0)
639 bison_ = true;
640 #endif
641 else {
642 fprintf(stderr, "invalid name for -g\n");
643 return 1;
644 }
645 break;
646
647 case 'n':
648 if (false);
649 else if (strcmp(arg, "minify") == 0)
650 pretty_ = true;
651 else {
652 fprintf(stderr, "invalid name for -n\n");
653 return 1;
654 }
655 break;
656
657 #ifdef CY_ATTACH
658 case 'p': {
659 size_t size(strlen(arg));
660 char *end;
661
662 pid = strtoul(arg, &end, 0);
663 if (arg + size != end) {
664 // XXX: arg needs to be escaped in some horrendous way of doom
665 const char *command(apr_psprintf(pool, "ps axc|sed -e '/^ *[0-9]/{s/^ *\\([0-9]*\\)\\( *[^ ]*\\)\\{3\\} *-*\\([^ ]*\\)/\\3 \\1/;/^%s /{s/^[^ ]* //;q;};};d'", arg));
666
667 if (FILE *pids = popen(command, "r")) {
668 char value[32];
669 size = 0;
670
671 for (;;) {
672 size_t read(fread(value + size, 1, sizeof(value) - size, pids));
673 if (read == 0)
674 break;
675 else {
676 size += read;
677 if (size == sizeof(value)) {
678 pid = _not(pid_t);
679 goto fail;
680 }
681 }
682 }
683
684 size:
685 if (size == 0)
686 goto fail;
687 if (value[size - 1] == '\n') {
688 --size;
689 goto size;
690 }
691
692 value[size] = '\0';
693 size = strlen(value);
694 pid = strtoul(value, &end, 0);
695 if (value + size != end) fail:
696 pid = _not(pid_t);
697 _syscall(pclose(pids));
698 }
699
700 if (pid == _not(pid_t)) {
701 fprintf(stderr, "invalid pid for -p\n");
702 return 1;
703 }
704 }
705 } break;
706 #endif
707
708 case 's':
709 strict_ = true;
710 break;
711 }
712 } getopt:;
713
714 const char *script;
715 int ind(state->ind);
716
717 #ifdef CY_ATTACH
718 if (pid != _not(pid_t) && ind < argc - 1) {
719 fprintf(stderr, "-p cannot set argv\n");
720 return 1;
721 }
722
723 if (pid != _not(pid_t) && compile) {
724 fprintf(stderr, "-p conflicts with -c\n");
725 return 1;
726 }
727 #endif
728
729 if (ind == argc)
730 script = NULL;
731 else {
732 #ifdef CY_EXECUTE
733 // XXX: const_cast?! wtf gcc :(
734 CYSetArgs(argc - ind - 1, const_cast<const char **>(argv + ind + 1));
735 #endif
736 script = argv[ind];
737 if (strcmp(script, "-") == 0)
738 script = NULL;
739 }
740
741 #ifdef CY_ATTACH
742 if (pid != _not(pid_t) && script == NULL && !tty) {
743 fprintf(stderr, "non-terminal attaching to remote console\n");
744 return 1;
745 }
746 #endif
747
748 #ifdef CY_ATTACH
749 if (pid == _not(pid_t))
750 client_ = -1;
751 else {
752 int server(_syscall(socket(PF_UNIX, SOCK_STREAM, 0))); try {
753 struct sockaddr_un address;
754 memset(&address, 0, sizeof(address));
755 address.sun_family = AF_UNIX;
756
757 sprintf(address.sun_path, "/tmp/.s.cy.%u", getpid());
758
759 _syscall(bind(server, reinterpret_cast<sockaddr *>(&address), SUN_LEN(&address)));
760 _syscall(chmod(address.sun_path, 0777));
761
762 try {
763 _syscall(listen(server, 1));
764 InjectLibrary(pid);
765 client_ = _syscall(accept(server, NULL, NULL));
766 } catch (...) {
767 // XXX: exception?
768 unlink(address.sun_path);
769 throw;
770 }
771 } catch (...) {
772 _syscall(close(server));
773 throw;
774 }
775 }
776 #else
777 client_ = -1;
778 #endif
779
780 if (script == NULL && tty)
781 Console(options);
782 else {
783 CYLocalPool pool;
784 CYDriver driver(script ?: "<stdin>");
785 cy::parser parser(driver);
786 Setup(driver, parser);
787
788 char *start, *end;
789
790 if (script == NULL) {
791 start = NULL;
792 end = NULL;
793
794 driver.file_ = stdin;
795 } else {
796 size_t size;
797 start = reinterpret_cast<char *>(Map(script, &size));
798 end = start + size;
799
800 if (size >= 2 && start[0] == '#' && start[1] == '!') {
801 start += 2;
802
803 if (void *line = memchr(start, '\n', end - start))
804 start = reinterpret_cast<char *>(line);
805 else
806 start = end;
807 }
808
809 driver.data_ = start;
810 driver.size_ = end - start;
811 }
812
813 if (parser.parse() != 0 || !driver.errors_.empty()) {
814 for (CYDriver::Errors::const_iterator i(driver.errors_.begin()); i != driver.errors_.end(); ++i)
815 std::cerr << i->location_.begin << ": " << i->message_ << std::endl;
816 } else if (driver.program_ != NULL)
817 if (client_ != -1) {
818 std::string code(start, end-start);
819 Run(client_, code, stdout);
820 } else {
821 std::ostringstream str;
822 CYOutput out(str, options);
823 Setup(out, driver, options);
824 out << *driver.program_;
825 std::string code(str.str());
826 if (compile)
827 std::cout << code;
828 else
829 Run(client_, code, stdout);
830 }
831 }
832
833 return 0;
834 }
835
836 int main(int argc, char const * const argv[], char const * const envp[]) {
837 apr_status_t status(apr_app_initialize(&argc, &argv, &envp));
838
839 if (status != APR_SUCCESS) {
840 fprintf(stderr, "apr_app_initialize() != APR_SUCCESS\n");
841 return 1;
842 } else try {
843 return Main(argc, argv, envp);
844 } catch (const CYException &error) {
845 CYPool pool;
846 fprintf(stderr, "%s\n", error.PoolCString(pool));
847 return 1;
848 }
849 }