2020-08-22 22:30:12 -04:00
|
|
|
/*!
|
2020-11-21 12:52:38 -05:00
|
|
|
* @file TextDB.cpp
|
2020-08-22 22:30:12 -04:00
|
|
|
* The Text Database for storing source code text.
|
|
|
|
* This allows us to ask for things like "where did this form come from?"
|
|
|
|
* and be able to print the file name and offset into the file, as well as the line.
|
|
|
|
*
|
|
|
|
* The purpose is to be able to have an error message like:
|
|
|
|
*
|
|
|
|
* Error on (+ a b): a has invalid type (string)
|
|
|
|
* From my-file.gc, line 25:
|
|
|
|
* (+ 1 (+ a b)) ; compute the sum
|
|
|
|
*/
|
|
|
|
|
2020-09-10 20:03:31 -04:00
|
|
|
#include "common/util/FileUtil.h"
|
2021-08-24 22:15:26 -04:00
|
|
|
#include "third-party/fmt/core.h"
|
2020-08-22 22:30:12 -04:00
|
|
|
|
|
|
|
#include "TextDB.h"
|
|
|
|
|
|
|
|
namespace goos {
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Initialize with the given string
|
|
|
|
*/
|
2021-08-26 20:33:00 -04:00
|
|
|
SourceText::SourceText(std::string r) : m_text(std::move(r)) {
|
2020-08-22 22:30:12 -04:00
|
|
|
// find line breaks
|
|
|
|
build_offsets();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Update line break data. Should be called any time the text is updated.
|
|
|
|
*/
|
|
|
|
void SourceText::build_offsets() {
|
2021-08-26 20:33:00 -04:00
|
|
|
m_offset_by_line.clear();
|
|
|
|
m_offset_by_line.push_back(0);
|
|
|
|
for (uint32_t i = 0; i < m_text.size(); i++) {
|
|
|
|
if (m_text[i] == '\n') {
|
|
|
|
m_offset_by_line.push_back(i);
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
}
|
2021-08-26 20:33:00 -04:00
|
|
|
m_offset_by_line.push_back(m_text.size());
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Get the text of the line containing the character at position "offset" from this source.
|
|
|
|
*/
|
|
|
|
std::string SourceText::get_line_containing_offset(int offset) {
|
|
|
|
auto range = get_containing_line(offset);
|
|
|
|
int start_offset = range.first ? 1 : 0;
|
2021-08-26 20:33:00 -04:00
|
|
|
return m_text.substr(range.first + start_offset,
|
|
|
|
std::max(0, range.second - range.first - start_offset));
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Get the index of the line containing the character at position "offset".
|
|
|
|
* O(n_lines) crappy implementation.
|
|
|
|
* Error if not found.
|
|
|
|
*/
|
|
|
|
int SourceText::get_line_idx(int offset) {
|
2021-08-26 20:33:00 -04:00
|
|
|
for (uint32_t line = 0; line < m_offset_by_line.size() - 1; line++) {
|
|
|
|
if (offset >= m_offset_by_line[line] && offset <= m_offset_by_line[line + 1]) {
|
2020-08-22 22:30:12 -04:00
|
|
|
return line;
|
|
|
|
}
|
|
|
|
}
|
2020-09-13 21:10:43 -04:00
|
|
|
throw std::runtime_error("Unable to get line index for character at position " +
|
|
|
|
std::to_string(offset));
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
|
2021-08-24 22:15:26 -04:00
|
|
|
int SourceText::get_offset_of_line(int line_idx) {
|
2021-08-26 20:33:00 -04:00
|
|
|
return m_offset_by_line.at(line_idx);
|
2021-08-24 22:15:26 -04:00
|
|
|
}
|
|
|
|
|
2020-08-22 22:30:12 -04:00
|
|
|
/*!
|
|
|
|
* Gets the [start, end) character offset of the line containing the given offset.
|
|
|
|
*/
|
|
|
|
std::pair<int, int> SourceText::get_containing_line(int offset) {
|
2021-08-26 20:33:00 -04:00
|
|
|
for (uint32_t line = 0; line < m_offset_by_line.size() - 1; line++) {
|
|
|
|
if (offset >= m_offset_by_line[line] && offset <= m_offset_by_line[line + 1]) {
|
|
|
|
return std::make_pair(m_offset_by_line[line], m_offset_by_line[line + 1]);
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
}
|
2021-08-26 20:33:00 -04:00
|
|
|
return std::make_pair(0, (int)m_text.size());
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Read text from a file.
|
|
|
|
*/
|
2021-08-24 22:15:26 -04:00
|
|
|
FileText::FileText(const std::string& filename, const std::string& description_name)
|
|
|
|
: m_filename(filename), m_desc_name(description_name) {
|
2021-08-26 20:33:00 -04:00
|
|
|
m_text = file_util::read_text_file(m_filename);
|
2020-08-22 22:30:12 -04:00
|
|
|
build_offsets();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Inform the TextDB about a source of text.
|
|
|
|
*/
|
|
|
|
void TextDb::insert(const std::shared_ptr<SourceText>& frag) {
|
2021-08-26 20:33:00 -04:00
|
|
|
m_fragments.push_back(frag);
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Link the GOOS object o to the offset into the given text fragment.
|
|
|
|
* The object _must_ be a pair or empty list.
|
|
|
|
*/
|
|
|
|
void TextDb::link(const Object& o, std::shared_ptr<SourceText> frag, int offset) {
|
|
|
|
if (o.is_empty_list())
|
|
|
|
return;
|
|
|
|
assert(o.is_pair());
|
|
|
|
TextRef ref;
|
|
|
|
ref.offset = offset;
|
|
|
|
ref.frag = std::move(frag);
|
2021-08-26 20:33:00 -04:00
|
|
|
m_map[o.heap_obj] = ref;
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Given an object, get a string representing where it's from. Or "?" if we can't find it.
|
|
|
|
*/
|
2021-03-04 10:33:59 -05:00
|
|
|
std::string TextDb::get_info_for(const Object& o, bool* terminate_compiler_error) const {
|
2020-08-22 22:30:12 -04:00
|
|
|
if (o.is_pair()) {
|
2021-08-26 20:33:00 -04:00
|
|
|
auto kv = m_map.find(o.heap_obj);
|
|
|
|
if (kv != m_map.end()) {
|
2020-12-01 21:39:46 -05:00
|
|
|
if (terminate_compiler_error) {
|
|
|
|
*terminate_compiler_error = kv->second.frag->terminate_compiler_error();
|
|
|
|
}
|
2020-08-22 22:30:12 -04:00
|
|
|
return get_info_for(kv->second.frag, kv->second.offset);
|
|
|
|
} else {
|
2020-12-01 21:39:46 -05:00
|
|
|
if (terminate_compiler_error) {
|
|
|
|
*terminate_compiler_error = false;
|
|
|
|
}
|
|
|
|
return "?\n";
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
} else {
|
2020-12-01 21:39:46 -05:00
|
|
|
if (terminate_compiler_error) {
|
|
|
|
*terminate_compiler_error = false;
|
|
|
|
}
|
|
|
|
return "?\n";
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* Given a source text and an offset, print a description of where it is.
|
|
|
|
*/
|
2021-03-04 10:33:59 -05:00
|
|
|
std::string TextDb::get_info_for(const std::shared_ptr<SourceText>& frag, int offset) const {
|
2021-08-24 22:15:26 -04:00
|
|
|
int line_idx = frag->get_line_idx(offset);
|
|
|
|
std::string result = frag->get_description() + ", line: " + std::to_string(line_idx + 1) + "\n";
|
2020-08-22 22:30:12 -04:00
|
|
|
result += frag->get_line_containing_offset(offset) + "\n";
|
2021-08-24 22:15:26 -04:00
|
|
|
int offset_in_line = std::max(offset - frag->get_offset_of_line(line_idx), 1) - 1;
|
|
|
|
|
|
|
|
std::string pointer(offset_in_line, ' ');
|
|
|
|
pointer += "^\n";
|
|
|
|
return result + pointer;
|
2020-08-22 22:30:12 -04:00
|
|
|
}
|
2021-03-04 10:33:59 -05:00
|
|
|
|
2021-08-26 20:33:00 -04:00
|
|
|
std::optional<TextDb::ShortInfo> TextDb::try_get_short_info(const Object& o) const {
|
|
|
|
if (o.is_pair()) {
|
|
|
|
auto it = m_map.find(o.heap_obj);
|
|
|
|
if (it != m_map.end()) {
|
|
|
|
auto& frag = it->second.frag;
|
|
|
|
// shorten the string
|
|
|
|
std::string name = frag->get_description();
|
|
|
|
size_t start = 0;
|
|
|
|
for (size_t i = 0; i < name.size(); i++) {
|
|
|
|
if (name[i] == '/' || name[i] == '\\') {
|
|
|
|
start = i + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (start < name.size()) {
|
|
|
|
name = name.substr(start);
|
|
|
|
}
|
|
|
|
|
|
|
|
ShortInfo result;
|
|
|
|
result.filename = name;
|
|
|
|
|
|
|
|
int line_idx = frag->get_line_idx(it->second.offset);
|
|
|
|
result.line_idx_to_display = line_idx + 1;
|
|
|
|
|
|
|
|
int offset_of_line = frag->get_offset_of_line(line_idx);
|
|
|
|
int offset_of_next_line = frag->get_offset_of_line(line_idx + 1);
|
|
|
|
|
|
|
|
int line_length = offset_of_next_line - offset_of_line;
|
|
|
|
|
|
|
|
int start_offset_in_line = it->second.offset - offset_of_line - 1;
|
|
|
|
result.pos_in_line = std::max(start_offset_in_line, 0);
|
|
|
|
result.line_text = std::string(frag->get_text() + offset_of_line + 1, line_length - 1);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TextDb::has_info(const Object& o) const {
|
|
|
|
return o.is_pair() && (m_map.find(o.heap_obj) != m_map.end());
|
|
|
|
}
|
|
|
|
|
2021-03-04 10:33:59 -05:00
|
|
|
/*!
|
|
|
|
* Make child have the same location in the source as parent. For example, if parent generates
|
|
|
|
* code that we want to be associated with the parent's location in source.
|
|
|
|
*
|
|
|
|
* Note: this only has an effect if both parent and child are pair/list. Otherwise it does nothing.
|
|
|
|
*/
|
|
|
|
void TextDb::inherit_info(const Object& parent, const Object& child) {
|
|
|
|
if (parent.is_pair() && child.is_pair()) {
|
2021-08-26 20:33:00 -04:00
|
|
|
auto parent_kv = m_map.find(parent.heap_obj);
|
|
|
|
if (parent_kv != m_map.end()) {
|
2021-03-11 12:54:16 -05:00
|
|
|
std::vector<const Object*> children = {&child};
|
|
|
|
// mark all forms as children. This will help with error messages in macros, and makes
|
|
|
|
// (add-macro-to-autocomplete) work properly.
|
|
|
|
while (!children.empty()) {
|
|
|
|
auto top = children.back();
|
|
|
|
children.pop_back();
|
2021-08-26 20:33:00 -04:00
|
|
|
if (m_map.insert({top->heap_obj, parent_kv->second}).second) {
|
2021-08-24 22:15:26 -04:00
|
|
|
if (top->as_pair()->car.is_pair()) {
|
|
|
|
children.push_back(&top->as_pair()->car);
|
|
|
|
}
|
|
|
|
if (top->as_pair()->cdr.is_pair()) {
|
|
|
|
children.push_back(&top->as_pair()->cdr);
|
|
|
|
}
|
2021-03-11 12:54:16 -05:00
|
|
|
}
|
|
|
|
}
|
2021-03-04 10:33:59 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-22 22:30:12 -04:00
|
|
|
} // namespace goos
|