blob: a1c10e5309ef10ba1261830efbfa2d531644e076 [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 "src/developer/shell/console/command.h"
#include <lib/syslog/cpp/macros.h>
#include <stdlib.h>
#include <iomanip>
#include <regex>
#include <sstream>
#include <string_view>
#include <vector>
#include "src/developer/shell/parser/ast.h"
#include "src/developer/shell/parser/parser.h"
namespace shell::console {
// TODO: Change the file ID to something useful.
Command::Command() : accumulated_nodes_(1) {}
Command::~Command() = default;
namespace {
// Walk a parse tree for errors and collect their messages into the given output stringstream.
void CollectErrors(std::string_view line, const parser::ast::Node& node, std::stringstream* out) {
if (auto err = node.AsError()) {
size_t err_end = err->start() + err->Size();
size_t line_start_offset = 0;
size_t line_start = 1;
for (size_t i = 0; i < err->start(); i++) {
if (line[i] == '\n') {
line_start_offset = i + 1;
line_start++;
}
}
size_t line_end = line_start;
for (size_t i = line_start_offset; i < err_end; i++) {
if (line[i] == '\n') {
line_end++;
}
}
size_t line_pad = std::to_string(line_end).size();
size_t line_number = line_start;
size_t start = line_start_offset;
do {
auto prev_width = out->width(line_pad);
(*out) << line_number;
out->width(prev_width);
(*out) << ": ";
size_t end = line.find('\n', start);
if (end == std::string::npos) {
end = line.size();
}
(*out) << line.substr(start, end - start) << "\n" << std::string(line_pad + 2, ' ');
for (size_t i = start; i <= end; i++) {
if (i == err->start()) {
(*out) << "^";
if (i != err_end) {
continue;
}
}
if (i < err->start()) {
(*out) << " ";
} else if (i == err_end) {
(*out) << " " << err->message();
break;
} else {
(*out) << "~";
}
}
(*out) << "\n\n";
start = end + 1;
line_number += 1;
} while (start <= err_end);
} else {
for (const auto& child : node.Children()) {
CollectErrors(line, *child, out);
}
}
}
// Walk a parse tree for errors and collect their messages.
std::string CollectErrors(std::string_view line, const parser::ast::Node& node) {
std::stringstream out;
CollectErrors(line, node, &out);
return out.str();
}
struct IdAndType {
AstBuilder::NodeId id;
llcpp::fuchsia::shell::ShellType type;
};
// Visitor for loading a parser AST into a FIDL AST.
class NodeASTVisitor : public parser::ast::NodeVisitor<IdAndType> {
public:
explicit NodeASTVisitor(AstBuilder* builder) : builder_(builder) {}
IdAndType VisitNode(const parser::ast::Node& node) override {
FX_NOTREACHED() << "Parser produced unknown node type.";
return {};
}
IdAndType VisitProgram(const parser::ast::Program& node) override {
// TODO: Multiple statements.
for (const auto& child : node.Children()) {
if (auto ch = child->AsVariableDecl()) {
auto ret = VisitVariableDecl(*ch);
// Return the value of the variable to the command line when done evaluating.
builder_->AddEmitResult(builder_->AddVariable(ch->identifier()));
return ret;
}
}
FX_NOTREACHED();
return {};
}
IdAndType VisitVariableDecl(const parser::ast::VariableDecl& node) override {
IdAndType expression = node.expression()->Visit(this);
AstBuilder::NodeId id = builder_->AddVariableDeclaration(
node.identifier(), std::move(expression.type), expression.id, false);
return {.id = id, .type = llcpp::fuchsia::shell::ShellType()};
}
IdAndType VisitInteger(const parser::ast::Integer& node) override {
AstBuilder::NodeId id = builder_->AddIntegerLiteral(node.value());
fidl::ObjectView<llcpp::fuchsia::shell::BuiltinType> builtin_type(
builder_->allocator(), llcpp::fuchsia::shell::BuiltinType::INTEGER);
return {.id = id, .type = llcpp::fuchsia::shell::ShellType::WithBuiltinType(builtin_type)};
}
IdAndType VisitIdentifier(const parser::ast::Identifier& node) override {
FX_NOTREACHED() << "Variable fetches are unimplemented." << node.identifier();
return {};
}
IdAndType VisitPath(const parser::ast::Path& node) override {
FX_NOTREACHED() << "Paths are unimplemented.";
return {};
}
IdAndType VisitAddSub(const parser::ast::AddSub& node) override {
FX_DCHECK(node.type() == parser::ast::AddSub::kAdd) << "Subtraction is unimplemented.";
AstBuilder::NodeId a_id = node.a()->Visit(this).id;
IdAndType b_value = node.b()->Visit(this);
AstBuilder::NodeId b_id = b_value.id;
AstBuilder::NodeId id = builder_->AddAddition(/*with_exceptions=*/false, a_id, b_id);
return {.id = id, .type = std::move(b_value.type)};
}
IdAndType VisitExpression(const parser::ast::Expression& node) override {
FX_DCHECK(node.Children().size() > 0);
return node.Children()[0]->Visit(this);
}
IdAndType VisitString(const parser::ast::String& node) override {
AstBuilder::NodeId id = builder_->AddStringLiteral(node.value());
return {.id = id,
.type = llcpp::fuchsia::shell::ShellType::WithBuiltinType(
builder_->allocator(), llcpp::fuchsia::shell::BuiltinType::STRING)};
}
IdAndType VisitObject(const parser::ast::Object& node) override {
builder_->OpenObject();
for (const auto& field : node.fields()) {
field->Visit(this);
}
auto result = builder_->CloseObject();
AstBuilder::NodeId id = result.value_node;
return {.id = id,
.type = llcpp::fuchsia::shell::ShellType::WithObjectSchema(builder_->allocator(),
result.schema_node)};
}
IdAndType VisitField(const parser::ast::Field& node) override {
IdAndType value = node.value()->Visit(this);
builder_->AddField(node.name(), value.id, std::move(value.type));
return {};
}
private:
AstBuilder* builder_;
};
} // namespace
bool Command::Parse(const std::string& line) {
if (line.empty()) {
return true;
}
auto node = parser::Parse(line);
FX_DCHECK(node) << "Error handling failed.";
if (node->HasErrors()) {
parse_error_ = CollectErrors(line, *node);
return false;
}
auto program = node->AsProgram();
FX_DCHECK(program) << "Parse did not yield a program node!";
NodeASTVisitor visitor(&accumulated_nodes_);
IdAndType value = program->Visit(&visitor);
accumulated_nodes_.SetRoot(value.id);
return true;
}
} // namespace shell::console