1 /*
2 * Copyright (c) 2018 Philip Chimento <philip.chimento@gmail.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to
6 * deal in the Software without restriction, including without limitation the
7 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8 * sell copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 * IN THE SOFTWARE.
21 *
22 * Authored By: Philip Chimento <philip.chimento@gmail.com>
23 */
24
25 #include <config.h> // for HAVE_READLINE_READLINE_H, HAVE_UNISTD_H
26
27 #include <stdint.h>
28 #include <stdio.h> // for feof, fflush, fgets, stdin, stdout
29
30 #ifdef HAVE_READLINE_READLINE_H
31 # include <readline/history.h>
32 # include <readline/readline.h>
33 #endif
34
35 #ifdef HAVE_UNISTD_H
36 # include <unistd.h> // for isatty, STDIN_FILENO
37 #elif defined(_WIN32)
38 # include <io.h>
39 # ifndef STDIN_FILENO
40 # define STDIN_FILENO 0
41 # endif
42 #endif
43
44 #include <glib.h>
45
46 #include <js/CallArgs.h>
47 #include <js/PropertySpec.h>
48 #include <js/RootingAPI.h>
49 #include <js/TypeDecls.h>
50 #include <js/Utility.h> // for UniqueChars
51 #include <js/Value.h>
52 #include <jsapi.h> // for JS_DefineFunctions, JS_NewStringCopyZ
53
54 #include "cjs/atoms.h"
55 #include "cjs/context-private.h"
56 #include "cjs/context.h"
57 #include "cjs/global.h"
58 #include "cjs/jsapi-util-args.h"
59 #include "cjs/jsapi-util.h"
60 #include "cjs/macros.h"
61
62 GJS_JSAPI_RETURN_CONVENTION
quit(JSContext * cx,unsigned argc,JS::Value * vp)63 static bool quit(JSContext* cx, unsigned argc, JS::Value* vp) {
64 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
65
66 int32_t exitcode;
67 if (!gjs_parse_call_args(cx, "quit", args, "i", "exitcode", &exitcode))
68 return false;
69
70 GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
71 gjs->exit(exitcode);
72 return false; // without gjs_throw() == "throw uncatchable exception"
73 }
74
75 GJS_JSAPI_RETURN_CONVENTION
do_readline(JSContext * cx,unsigned argc,JS::Value * vp)76 static bool do_readline(JSContext* cx, unsigned argc, JS::Value* vp) {
77 JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
78
79 JS::UniqueChars prompt;
80 if (!gjs_parse_call_args(cx, "readline", args, "|s", "prompt", &prompt))
81 return false;
82
83 GjsAutoChar line;
84 do {
85 const char* real_prompt = prompt ? prompt.get() : "db> ";
86 #ifdef HAVE_READLINE_READLINE_H
87 if (isatty(STDIN_FILENO)) {
88 line = readline(real_prompt);
89 } else {
90 #else
91 {
92 #endif // HAVE_READLINE_READLINE_H
93 char buf[256];
94 g_print("%s", real_prompt);
95 fflush(stdout);
96 if (!fgets(buf, sizeof buf, stdin))
97 buf[0] = '\0';
98 line.reset(g_strchomp(g_strdup(buf)));
99
100 if (!isatty(STDIN_FILENO)) {
101 if (feof(stdin)) {
102 g_print("[quit due to end of input]\n");
103 line.reset(g_strdup("quit"));
104 } else {
105 g_print("%s\n", line.get());
106 }
107 }
108 }
109
110 /* EOF, return null */
111 if (!line) {
112 args.rval().setUndefined();
113 return true;
114 }
115 } while (line && line.get()[0] == '\0');
116
117 /* Add line to history and convert it to a JSString so that we can pass it
118 * back as the return value */
119 #ifdef HAVE_READLINE_READLINE_H
120 add_history(line);
121 #endif
122 args.rval().setString(JS_NewStringCopyZ(cx, line));
123 return true;
124 }
125
126 // clang-format off
127 static JSFunctionSpec debugger_funcs[] = {
128 JS_FN("quit", quit, 1, GJS_MODULE_PROP_FLAGS),
129 JS_FN("readline", do_readline, 1, GJS_MODULE_PROP_FLAGS),
130 JS_FS_END
131 };
132 // clang-format on
133
134 void gjs_context_setup_debugger_console(GjsContext* gjs) {
135 auto cx = static_cast<JSContext*>(gjs_context_get_native_context(gjs));
136
137 JS::RootedObject debuggee(cx, gjs_get_import_global(cx));
138 JS::RootedObject debugger_global(
139 cx, gjs_create_global_object(cx, GjsGlobalType::DEBUGGER));
140
141 // Enter realm of the debugger and initialize it with the debuggee
142 JSAutoRealm ar(cx, debugger_global);
143 JS::RootedObject debuggee_wrapper(cx, debuggee);
144 if (!JS_WrapObject(cx, &debuggee_wrapper)) {
145 gjs_log_exception(cx);
146 return;
147 }
148
149 const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
150 JS::RootedValue v_wrapper(cx, JS::ObjectValue(*debuggee_wrapper));
151 if (!JS_SetPropertyById(cx, debugger_global, atoms.debuggee(), v_wrapper) ||
152 !JS_DefineFunctions(cx, debugger_global, debugger_funcs) ||
153 !gjs_define_global_properties(cx, debugger_global,
154 GjsGlobalType::DEBUGGER, "GJS debugger",
155 "debugger"))
156 gjs_log_exception(cx);
157 }
158