blob: 65a294e6da68510c7f3145fdecda04ae566ab854 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "repl.h"
#include <stdio.h>
#include <string.h>
#include <cctype>
#include <iostream>
#include <set>
#include <sstream>
#include <string>
#include <vector>
namespace shell::repl {
Repl::Repl(JSContext* ctx, const std::string& prompt)
: li_([this](const std::string& s) { HandleLine(s); }, prompt),
ctx_(ctx),
output_(&std::cout),
exit_shell_cmd_(false),
running_(false) {
Write("Type \\h for help\n");
li_.SetAutocompleteCallback([this](std::string line_to_complete) {
this->line_to_complete_ = line_to_complete;
std::string script = "repl.autocompletionCallback();";
JSValue completions_as_js_str =
JS_Eval(ctx_, script.c_str(), script.length(), "<evalScript>", JS_EVAL_TYPE_GLOBAL);
const char* completions_as_cstr = JS_ToCString(ctx_, completions_as_js_str);
if (!completions_as_cstr) { // completion error, return an empty vector
return std::vector<std::string>();
}
std::string completions_as_str(completions_as_cstr);
std::vector<std::string> completions;
std::stringstream completions_as_stream(completions_as_str);
std::string cur_completion;
while (std::getline(completions_as_stream, cur_completion, '/')) {
completions.push_back(line_to_complete + cur_completion);
}
return completions;
});
li_.Show();
}
Repl::Repl(JSContext* ctx, const std::string& prompt, fit::function<void(const std::string&)> cb)
: li_(std::move(cb), prompt),
ctx_(ctx),
output_(&std::cout),
exit_shell_cmd_(false),
running_(false) {
Write("Type \\h for help\n");
li_.Show();
}
void Repl::Write(const char* output) { *output_ << output; }
void Repl::ChangeOutput(std::ostream* ss) { output_ = ss; }
void Repl::ShowPrompt() {
running_ = false;
li_.Show();
}
const char* Repl::GetCmd() { return cur_cmd_.c_str(); }
const char* Repl::GetLine() { return line_to_complete_.c_str(); }
bool Repl::FeedInput(uint8_t* bytes, size_t num_bytes) {
if (running_) {
if (num_bytes >= 1 &&
bytes[0] == 26) { // shell cmd 'c' to show the prompt in spite of running_
ShowPrompt();
}
return false;
}
for (size_t i = 0; i < num_bytes; i++) {
li_.OnInput(static_cast<char>(bytes[i]));
if (exit_shell_cmd_) {
return true;
}
if (running_) { // cmd is still running, we discard the rest of the input
return false;
}
}
return false;
}
void Repl::HandleLine(const std::string& line) {
li_.Hide();
running_ = true;
std::string cmd = mexpr_ + line;
std::string shell_cmd = GetAndExecuteShellCmd(cmd);
if (shell_cmd == "\\q") {
exit_shell_cmd_ = true;
} else if (!shell_cmd.empty()) {
mexpr_ = "";
exit_shell_cmd_ = false;
ShowPrompt();
} else {
std::string open_symbols = OpenSymbols(cmd);
if (open_symbols.empty()) {
mexpr_ = "";
li_.AddToHistory(cmd);
EvalCmd(cmd);
} else {
mexpr_ = cmd;
ShowPrompt();
}
exit_shell_cmd_ = false;
}
}
std::string Repl::GetAndExecuteShellCmd(std::string cmd) {
if (cmd.substr(0, 2) == "\\h") {
std::string help_msg =
"\\q\texit\n"
"\\h\tthis help\n"
"Ctrl-Z\tmake the (hidden) prompt show up when a previous command aborted with an error\n";
Write(help_msg.c_str());
return cmd.substr(0, 2);
}
if (cmd.substr(0, 2) == "\\q") {
return cmd.substr(0, 2);
}
return "";
}
void Repl::EvalCmd(const std::string& cmd) {
cur_cmd_ = cmd; // saving the cmd in the Repl class instance, to be executed through a JS call
std::string script = "repl.evalScriptAwaitsPromise()";
JSValue res = JS_Eval(ctx_, script.c_str(), script.length(), "<evalScript>", JS_EVAL_TYPE_GLOBAL);
if (JS_IsException(res)) { // the script above was at fault, that's bad
js_std_dump_error(ctx_);
ShowPrompt();
}
}
bool match(char a, char b) {
if (a == '(' && b == ')') {
return true;
}
if (a == '{' && b == '}') {
return true;
}
if (a == '[' && b == ']')
return true;
return false;
}
bool isIdentifierAllowed(char c) {
return std::isalpha(c) || std::isdigit(c) || c == '_' || c == '$';
}
std::string Repl::OpenSymbols(const std::string& cmd) {
std::string open_symbols;
bool regex_possible = true;
size_t n = cmd.length();
size_t i = 0;
while (i < n) {
if (cmd[i] == '\'' || cmd[i] == '\"' || cmd[i] == '`') { // string
char delim = cmd[i];
open_symbols.push_back(delim);
i++;
while (i < n) {
if (cmd[i] == '\\')
i++;
else if (cmd[i] == delim) {
open_symbols.pop_back();
i++;
break;
}
i++;
}
regex_possible = false;
}
else if (cmd[i] == '/') {
if (cmd[i + 1] == '*') { // block comment
std::size_t ip = cmd.find("*/", i + 1);
if (ip != std::string::npos)
i = ip + 1;
else {
open_symbols.push_back('*');
i = n;
}
} else if (cmd[i + 1] == '/') { // line comment
for (i += 2; i < n; i++) {
if (cmd[i] == '\n')
break;
}
i++;
} else if (regex_possible) { // regex
open_symbols.push_back('/');
for (i++; i < n; i++) {
if (cmd[i] == '\\') {
if (i < n)
i++;
} else if (open_symbols.back() == '[') { // ignore / or [ if within []
if (cmd[i] == ']')
open_symbols.pop_back();
} else if (cmd[i] == '[') {
open_symbols.push_back('[');
if (cmd[i] == '[' || cmd[i] == ']')
i++;
} else if (cmd[i] == '/') {
open_symbols.pop_back();
break;
}
}
i++;
regex_possible = false;
} else {
regex_possible = true;
i++;
}
}
else if (strchr("{[(", cmd[i])) {
open_symbols.push_back(cmd[i]);
regex_possible = true;
i++;
}
else if (strchr("}])", cmd[i])) {
regex_possible = false;
if (match(open_symbols.back(), cmd[i]) && !open_symbols.empty()) {
open_symbols.pop_back();
}
i++;
}
else if (std::isspace(cmd[i])) {
i++;
}
else if (strchr("+-", cmd[i])) {
regex_possible = true;
i++;
}
else if (std::isdigit(cmd[i])) { // a number
while (i < n && (std::isalnum(cmd[i]) || cmd[i] == '.' || cmd[i] == '+' || cmd[i] == '-')) {
i++;
}
}
else if (isIdentifierAllowed(cmd[i])) { // an identifier
regex_possible = true;
int old_i = i;
i++;
while (i < n && isIdentifierAllowed(cmd[i])) {
i++;
}
std::set<std::string> no_regex_keywords = {
"this", "super", "undefined", "null", "true", "false", "Infinity", "NaN", "arguments"};
std::set<std::string> keywords = {
"break", "case", "catch", "continue", "debugger", "default", "delete",
"do", "else", "finally", "for", "function", "if", "in",
"instanceof", "new", "return", "switch", "this", "throw", "try",
"typeof", "while", "with", "class", "const", "enum", "import",
"export", "extends", "super", "implements", "interface", "let", "package",
"private", "protected", "public", "static", "yield", "undefined", "null",
"true", "false", "Infinity", "NaN", "eval", "arguments", "await",
"void", "var"};
if (keywords.count(cmd.substr(old_i, i - old_i)) > 0) {
if (no_regex_keywords.count(cmd.substr(old_i, i - old_i)) > 0) {
regex_possible = false;
}
continue;
}
size_t ip = i;
while (ip < n && std::isspace(cmd[ip])) {
ip++;
}
if (ip < n && cmd[ip] == '(') { // beginning of function, regex possible
continue;
}
regex_possible = false;
} else {
regex_possible = true;
i++;
}
}
return open_symbols;
}
} // namespace shell::repl