]> git.saurik.com Git - cycript.git/blob - Console.cpp
596f804b19b4472b6374297cf2a85a6b38de6411
[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 static std::ostream *out_;
156
157 static void Write(bool syntax, const char *data, size_t size, std::ostream &out) {
158 if (syntax)
159 CYLexerHighlight(data, size, out);
160 else
161 out.write(data, size);
162 }
163
164 static void Output(bool syntax, CYUTF8String json, std::ostream *out, bool expand = false) {
165 const char *data(json.data);
166 size_t size(json.size);
167
168 if (data == NULL || out == NULL)
169 return;
170
171 if (!expand ||
172 data[0] != '@' && data[0] != '"' && data[0] != '\'' ||
173 data[0] == '@' && data[1] != '"' && data[1] != '\''
174 )
175 Write(syntax, data, size, *out);
176 else for (size_t i(0); i != size; ++i)
177 if (data[i] != '\\')
178 *out << data[i];
179 else switch(data[++i]) {
180 case '\0': goto done;
181 case '\\': *out << '\\'; break;
182 case '\'': *out << '\''; break;
183 case '"': *out << '"'; break;
184 case 'b': *out << '\b'; break;
185 case 'f': *out << '\f'; break;
186 case 'n': *out << '\n'; break;
187 case 'r': *out << '\r'; break;
188 case 't': *out << '\t'; break;
189 case 'v': *out << '\v'; break;
190 default: *out << '\\'; --i; break;
191 }
192
193 done:
194 *out << std::endl;
195 }
196
197 static void Run(int client, bool syntax, const char *data, size_t size, std::ostream *out = NULL, bool expand = false) {
198 CYPool pool;
199 Output(syntax, Run(pool, client, CYUTF8String(data, size)), out, expand);
200 }
201
202 static void Run(int client, bool syntax, std::string &code, std::ostream *out = NULL, bool expand = false) {
203 Run(client, syntax, code.c_str(), code.size(), out, expand);
204 }
205
206 int (*append_history$)(int, const char *);
207
208 static std::string command_;
209
210 static CYExpression *ParseExpression(CYUTF8String code) {
211 std::stringstream stream;
212 stream << '(' << code << ')';
213 CYDriver driver(stream);
214
215 cy::parser parser(driver);
216 Setup(driver, parser);
217
218 if (parser.parse() != 0 || !driver.errors_.empty())
219 return NULL;
220
221 CYOptions options;
222 CYContext context(options);
223
224 // XXX: this could be replaced with a CYStatement::Primitive()
225 if (CYExpress *express = dynamic_cast<CYExpress *>(driver.program_->statements_))
226 return express->expression_->Primitive(context);
227
228 return NULL;
229 }
230
231 static int client_;
232
233 static char **Complete(const char *word, int start, int end) {
234 rl_attempted_completion_over = TRUE;
235
236 CYLocalPool pool;
237
238 std::string line(rl_line_buffer, start);
239 std::istringstream stream(command_ + line);
240 CYDriver driver(stream);
241
242 driver.auto_ = true;
243
244 cy::parser parser(driver);
245 Setup(driver, parser);
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 *out_ << '\n';
317 Output(false, json, out_);
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 bool syntax(false);
408
409 out_ = &std::cout;
410
411 // rl_completer_word_break_characters is broken in libedit
412 rl_basic_word_break_characters = break_;
413
414 rl_completer_word_break_characters = break_;
415 rl_attempted_completion_function = &Complete;
416 rl_bind_key('\t', rl_complete);
417
418 struct sigaction action;
419 sigemptyset(&action.sa_mask);
420 action.sa_handler = &sigint;
421 action.sa_flags = 0;
422 sigaction(SIGINT, &action, NULL);
423
424 restart: for (;;) {
425 command_.clear();
426 std::vector<std::string> lines;
427
428 bool extra(false);
429 const char *prompt("cy# ");
430
431 if (setjmp(ctrlc_) != 0) {
432 mode_ = Working;
433 *out_ << std::endl;
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 *out_ << "bypass == " << (bypass ? "true" : "false") << std::endl;
453 } else if (data == "debug") {
454 debug = !debug;
455 *out_ << "debug == " << (debug ? "true" : "false") << std::endl;
456 } else if (data == "expand") {
457 expand = !expand;
458 *out_ << "expand == " << (expand ? "true" : "false") << std::endl;
459 } else if (data == "syntax") {
460 syntax = !syntax;
461 *out_ << "syntax == " << (syntax ? "true" : "false") << std::endl;
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
489 std::istringstream stream(command_);
490 CYDriver driver(stream);
491
492 cy::parser parser(driver);
493 Setup(driver, parser);
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 Write(syntax, code.c_str(), code.size(), std::cout);
550 std::cout << std::endl;
551 }
552
553 Run(client_, syntax, code, out_, expand);
554 }
555
556 if (append_history$ != NULL) {
557 _syscall(close(_syscall(open(histfile, O_CREAT | O_WRONLY, 0600))));
558 (*append_history$)(histlines, histfile);
559 } else {
560 write_history(histfile);
561 }
562
563 *out_ << std::endl;
564 }
565
566 static void *Map(const char *path, size_t *psize) {
567 int fd;
568 _syscall(fd = open(path, O_RDONLY));
569
570 struct stat stat;
571 _syscall(fstat(fd, &stat));
572 size_t size(stat.st_size);
573
574 *psize = size;
575
576 void *base;
577 _syscall(base = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0));
578
579 _syscall(close(fd));
580 return base;
581 }
582
583 void InjectLibrary(pid_t pid);
584
585 int Main(int argc, char const * const argv[], char const * const envp[]) {
586 bool tty(isatty(STDIN_FILENO));
587 bool compile(false);
588 CYOptions options;
589
590 append_history$ = (int (*)(int, const char *)) (dlsym(RTLD_DEFAULT, "append_history"));
591
592 #ifdef CY_ATTACH
593 pid_t pid(_not(pid_t));
594 #endif
595
596 CYPool pool;
597 apr_getopt_t *state;
598 _aprcall(apr_getopt_init(&state, pool, argc, argv));
599
600 for (;;) {
601 char opt;
602 const char *arg;
603
604 apr_status_t status(apr_getopt(state,
605 "cg:n:"
606 #ifdef CY_ATTACH
607 "p:"
608 #endif
609 "s"
610 , &opt, &arg));
611
612 switch (status) {
613 case APR_EOF:
614 goto getopt;
615 case APR_BADCH:
616 case APR_BADARG:
617 fprintf(stderr,
618 "usage: cycript [-c]"
619 #ifdef CY_ATTACH
620 " [-p <pid|name>]"
621 #endif
622 " [<script> [<arg>...]]\n"
623 );
624 return 1;
625 default:
626 _aprcall(status);
627 }
628
629 switch (opt) {
630 case 'c':
631 compile = true;
632 break;
633
634 case 'g':
635 if (false);
636 else if (strcmp(arg, "rename") == 0)
637 options.verbose_ = true;
638 #if YYDEBUG
639 else if (strcmp(arg, "bison") == 0)
640 bison_ = true;
641 #endif
642 else {
643 fprintf(stderr, "invalid name for -g\n");
644 return 1;
645 }
646 break;
647
648 case 'n':
649 if (false);
650 else if (strcmp(arg, "minify") == 0)
651 pretty_ = true;
652 else {
653 fprintf(stderr, "invalid name for -n\n");
654 return 1;
655 }
656 break;
657
658 #ifdef CY_ATTACH
659 case 'p': {
660 size_t size(strlen(arg));
661 char *end;
662
663 pid = strtoul(arg, &end, 0);
664 if (arg + size != end) {
665 // XXX: arg needs to be escaped in some horrendous way of doom
666 const char *command(apr_psprintf(pool, "ps axc|sed -e '/^ *[0-9]/{s/^ *\\([0-9]*\\)\\( *[^ ]*\\)\\{3\\} *-*\\([^ ]*\\)/\\3 \\1/;/^%s /{s/^[^ ]* //;q;};};d'", arg));
667
668 if (FILE *pids = popen(command, "r")) {
669 char value[32];
670 size = 0;
671
672 for (;;) {
673 size_t read(fread(value + size, 1, sizeof(value) - size, pids));
674 if (read == 0)
675 break;
676 else {
677 size += read;
678 if (size == sizeof(value)) {
679 pid = _not(pid_t);
680 goto fail;
681 }
682 }
683 }
684
685 size:
686 if (size == 0)
687 goto fail;
688 if (value[size - 1] == '\n') {
689 --size;
690 goto size;
691 }
692
693 value[size] = '\0';
694 size = strlen(value);
695 pid = strtoul(value, &end, 0);
696 if (value + size != end) fail:
697 pid = _not(pid_t);
698 _syscall(pclose(pids));
699 }
700
701 if (pid == _not(pid_t)) {
702 fprintf(stderr, "invalid pid for -p\n");
703 return 1;
704 }
705 }
706 } break;
707 #endif
708
709 case 's':
710 strict_ = true;
711 break;
712 }
713 } getopt:;
714
715 const char *script;
716 int ind(state->ind);
717
718 #ifdef CY_ATTACH
719 if (pid != _not(pid_t) && ind < argc - 1) {
720 fprintf(stderr, "-p cannot set argv\n");
721 return 1;
722 }
723
724 if (pid != _not(pid_t) && compile) {
725 fprintf(stderr, "-p conflicts with -c\n");
726 return 1;
727 }
728 #endif
729
730 if (ind == argc)
731 script = NULL;
732 else {
733 #ifdef CY_EXECUTE
734 // XXX: const_cast?! wtf gcc :(
735 CYSetArgs(argc - ind - 1, const_cast<const char **>(argv + ind + 1));
736 #endif
737 script = argv[ind];
738 if (strcmp(script, "-") == 0)
739 script = NULL;
740 }
741
742 #ifdef CY_ATTACH
743 if (pid != _not(pid_t) && script == NULL && !tty) {
744 fprintf(stderr, "non-terminal attaching to remote console\n");
745 return 1;
746 }
747 #endif
748
749 #ifdef CY_ATTACH
750 if (pid == _not(pid_t))
751 client_ = -1;
752 else {
753 int server(_syscall(socket(PF_UNIX, SOCK_STREAM, 0))); try {
754 struct sockaddr_un address;
755 memset(&address, 0, sizeof(address));
756 address.sun_family = AF_UNIX;
757
758 sprintf(address.sun_path, "/tmp/.s.cy.%u", getpid());
759
760 _syscall(bind(server, reinterpret_cast<sockaddr *>(&address), SUN_LEN(&address)));
761 _syscall(chmod(address.sun_path, 0777));
762
763 try {
764 _syscall(listen(server, 1));
765 InjectLibrary(pid);
766 client_ = _syscall(accept(server, NULL, NULL));
767 } catch (...) {
768 // XXX: exception?
769 unlink(address.sun_path);
770 throw;
771 }
772 } catch (...) {
773 _syscall(close(server));
774 throw;
775 }
776 }
777 #else
778 client_ = -1;
779 #endif
780
781 if (script == NULL && tty)
782 Console(options);
783 else {
784 CYLocalPool pool;
785
786 char *start, *end;
787 std::istream *indirect;
788
789 if (script == NULL) {
790 start = NULL;
791 end = NULL;
792 indirect = &std::cin;
793 } else {
794 size_t size;
795 start = reinterpret_cast<char *>(Map(script, &size));
796 end = start + size;
797
798 if (size >= 2 && start[0] == '#' && start[1] == '!') {
799 start += 2;
800
801 if (void *line = memchr(start, '\n', end - start))
802 start = reinterpret_cast<char *>(line);
803 else
804 start = end;
805 }
806
807 indirect = NULL;
808 }
809
810 CYStream direct(start, end);
811 std::istream &stream(indirect == NULL ? direct : *indirect);
812 CYDriver driver(stream, script ?: "<stdin>");
813
814 cy::parser parser(driver);
815 Setup(driver, parser);
816
817 if (parser.parse() != 0 || !driver.errors_.empty()) {
818 for (CYDriver::Errors::const_iterator i(driver.errors_.begin()); i != driver.errors_.end(); ++i)
819 std::cerr << i->location_.begin << ": " << i->message_ << std::endl;
820 } else if (driver.program_ != NULL)
821 if (client_ != -1) {
822 // XXX: this code means that you can't pipe to another process
823 std::string code(start, end-start);
824 Run(client_, false, code, &std::cout);
825 } else {
826 std::ostringstream str;
827 CYOutput out(str, options);
828 Setup(out, driver, options);
829 out << *driver.program_;
830 std::string code(str.str());
831 if (compile)
832 std::cout << code;
833 else
834 Run(client_, false, code, &std::cout);
835 }
836 }
837
838 return 0;
839 }
840
841 int main(int argc, char const * const argv[], char const * const envp[]) {
842 apr_status_t status(apr_app_initialize(&argc, &argv, &envp));
843
844 if (status != APR_SUCCESS) {
845 fprintf(stderr, "apr_app_initialize() != APR_SUCCESS\n");
846 return 1;
847 } else try {
848 return Main(argc, argv, envp);
849 } catch (const CYException &error) {
850 CYPool pool;
851 fprintf(stderr, "%s\n", error.PoolCString(pool));
852 return 1;
853 }
854 }