[Compiler] Add get-info (#306)

* wip symbol info

* finish implementation
This commit is contained in:
water111
2021-03-04 10:33:59 -05:00
committed by GitHub
parent e77478e2e6
commit 9168f03289
14 changed files with 583 additions and 14 deletions
+17 -2
View File
@@ -108,7 +108,7 @@ void TextDb::link(const Object& o, std::shared_ptr<SourceText> frag, int offset)
/*!
* Given an object, get a string representing where it's from. Or "?" if we can't find it.
*/
std::string TextDb::get_info_for(const Object& o, bool* terminate_compiler_error) {
std::string TextDb::get_info_for(const Object& o, bool* terminate_compiler_error) const {
if (o.is_pair()) {
auto kv = map.find(o.heap_obj);
if (kv != map.end()) {
@@ -133,10 +133,25 @@ std::string TextDb::get_info_for(const Object& o, bool* terminate_compiler_error
/*!
* Given a source text and an offset, print a description of where it is.
*/
std::string TextDb::get_info_for(const std::shared_ptr<SourceText>& frag, int offset) {
std::string TextDb::get_info_for(const std::shared_ptr<SourceText>& frag, int offset) const {
std::string result = "text from " + frag->get_description() +
", line: " + std::to_string(frag->get_line_idx(offset) + 1) + "\n";
result += frag->get_line_containing_offset(offset) + "\n";
return result;
}
/*!
* 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()) {
auto parent_kv = map.find(parent.heap_obj);
if (parent_kv != map.end()) {
map[child.heap_obj] = parent_kv->second;
}
}
}
} // namespace goos
+3 -2
View File
@@ -91,8 +91,9 @@ class TextDb {
public:
void insert(const std::shared_ptr<SourceText>& frag);
void link(const Object& o, std::shared_ptr<SourceText> frag, int offset);
std::string get_info_for(const Object& o, bool* terminate_compiler_error = nullptr);
std::string get_info_for(const std::shared_ptr<SourceText>& frag, int offset);
std::string get_info_for(const Object& o, bool* terminate_compiler_error = nullptr) const;
std::string get_info_for(const std::shared_ptr<SourceText>& frag, int offset) const;
void inherit_info(const Object& parent, const Object& child);
private:
std::vector<std::shared_ptr<SourceText>> fragments;
+185
View File
@@ -0,0 +1,185 @@
#pragma once
#include <cassert>
#include <vector>
#include <string>
/*!
* A simple prefix tree. It works similarly to a map, but also supports fast lookups by prefix with
* the ability to lookup all entries with keys that begin with a given prefix.
*
* It owns the memory for the objects it stores.
* Doing an insert will create a copy of your object.
*
* Other that deleting the whole thing, there is no support for removing a node.
*/
template <typename T>
class Trie {
public:
Trie() = default;
Trie(const Trie&) = delete;
Trie& operator=(const Trie&) = delete;
// Insert an object, replacing an existing one if it exists
void insert(const std::string& str, const T& obj);
// Get the object at the string. Default construct a new one if none exists.
T* operator[](const std::string& str);
// Lookup an existing object. If none exists, return nullptr.
T* lookup(const std::string& str);
// return the number of entries.
int size() const { return m_size; }
// Get all objects starting with the given prefix.
std::vector<T*> lookup_prefix(const std::string& str);
~Trie();
private:
static constexpr int CHAR_START = ' ';
static constexpr int CHAR_END = '~' + 1;
static constexpr int CHAR_SIZE = CHAR_END - CHAR_START;
static int idx(char c) {
assert(c >= CHAR_START);
assert(c < CHAR_END);
return c - CHAR_START;
}
struct Node {
T* value = nullptr;
Node* children[CHAR_SIZE] = {0};
void delete_children() {
if (value) {
delete value;
value = nullptr;
}
for (auto child : children) {
if (child) {
child->delete_children();
delete child;
child = nullptr;
}
}
}
/*!
* Return true if a new object was inserted.
*/
bool insert(const char* str, const T* obj) {
if (!*str) {
// we are the child!
if (value) {
delete value;
value = new T(*obj);
return false; // didn't change the count.
} else {
value = new T(*obj);
return true; // did change the count
}
} else {
// still more to go
char first = *str;
if (!children[idx(first)]) {
children[idx(first)] = new Node();
}
return children[idx(first)]->insert(str + 1, obj);
}
}
T* bracket_operator(const char* str, bool* inserted) {
if (!*str) {
// we are the child!
if (!value) {
value = new T();
*inserted = true;
} else {
*inserted = false;
}
return value;
} else {
// still more to go
char first = *str;
if (!children[idx(first)]) {
children[idx(first)] = new Node();
}
return children[idx(first)]->bracket_operator(str + 1, inserted);
}
}
T* lookup(const char* str) {
if (!*str) {
return value;
}
if (children[idx(*str)]) {
return children[idx(*str)]->lookup(str + 1);
}
return nullptr;
}
void get_all_children(std::vector<T*>& result) {
if (value) {
result.push_back(value);
}
for (auto child : children) {
if (child) {
child->get_all_children(result);
}
}
}
std::vector<T*> lookup_prefix(const char* str) {
if (!*str) {
std::vector<T*> result;
get_all_children(result);
return result;
} else {
if (children[idx(*str)]) {
return children[idx(*str)]->lookup_prefix(str + 1);
} else {
return {};
}
}
}
};
Node m_root;
int m_size = 0;
};
template <typename T>
Trie<T>::~Trie<T>() {
m_root.delete_children();
m_size = 0;
}
template <typename T>
void Trie<T>::insert(const std::string& str, const T& obj) {
if (m_root.insert(str.c_str(), &obj)) {
m_size++;
}
}
template <typename T>
T* Trie<T>::lookup(const std::string& str) {
return m_root.lookup(str.c_str());
}
template <typename T>
T* Trie<T>::operator[](const std::string& str) {
bool added = false;
auto result = m_root.bracket_operator(str.c_str(), &added);
if (added) {
m_size++;
}
return result;
}
template <typename T>
std::vector<T*> Trie<T>::lookup_prefix(const std::string& str) {
return m_root.lookup_prefix(str.c_str());
}
+3 -1
View File
@@ -116,4 +116,6 @@
## V0.6
- There is no longer a separate compiler form for variable vs. constant shifts. Instead the compiler will pick the constant shift automatically when possible. The shifts are called `sar`, `shl` and `shr`, like the x86 instructions.
- Static type reference link data now has the correct number of methods. This prevents errors like `dkernel: trying to redefine a type 'game-info' with 29 methods when it had 12, try restarting` when you did not actually redefine the number of methods.
- Static type reference link data now has the correct number of methods. This prevents errors like `dkernel: trying to redefine a type 'game-info' with 29 methods when it had 12, try restarting` when you did not actually redefine the number of methods.
- Added `get-info` to figure out what something is and where it's defined.
- Added `autocomplete` to get auto-completions for a prefix.
+66
View File
@@ -625,6 +625,72 @@ Reload the GOAL compiler
```
Disconnect from the target and reset all compiler state. This is equivalent to exiting the compiler and opening it again.
## `get-info`
Get information about something.
```lisp
(get-info <something>)
```
Use `get-info` to see what something is and where it is defined.
For example:
```lisp
;; get info about a global variable:
g > (get-info *kernel-context*)
[Global Variable] Type: kernel-context Defined: text from goal_src/kernel/gkernel.gc, line: 88
(define *kernel-context* (new 'static 'kernel-context
;; get info about a function. This particular function is forward declared, so there's an entry for that too.
;; global functions are also global variables, so there's a global variable entry as well.
g > (get-info fact)
[Forward-Declared] Name: fact Defined: text from goal_src/kernel/gcommon.gc, line: 1098
(define-extern fact (function int int))
[Function] Name: fact Defined: text from kernel/gcommon.gc, line: 1099
(defun fact ((x int))
[Global Variable] Type: (function int int) Defined: text from goal_src/kernel/gcommon.gc, line: 1099
(defun fact ((x int))
;; get info about a type
g > (get-info kernel-context)
[Type] Name: kernel-context Defined: text from goal_src/kernel/gkernel-h.gc, line: 114
(deftype kernel-context (basic)
;; get info about a method
g > (get-info reset!)
[Method] Type: collide-sticky-rider-group Method Name: reset! Defined: text from goal_src/engine/collide/collide-shape-h.gc, line: 48
(defmethod reset! collide-sticky-rider-group ((obj collide-sticky-rider-group))
[Method] Type: collide-overlap-result Method Name: reset! Defined: text from goal_src/engine/collide/collide-shape-h.gc, line: 94
(defmethod reset! collide-overlap-result ((obj collide-overlap-result))
[Method] Type: load-state Method Name: reset! Defined: text from goal_src/engine/level/load-boundary.gc, line: 9
(defmethod reset! load-state ((obj load-state))
;; get info about a constant
g > (get-info TWO_PI)
[Constant] Name: TWO_PI Value: (the-as float #x40c90fda) Defined: text from goal_src/engine/math/trigonometry.gc, line: 34
(defconstant TWO_PI (the-as float #x40c90fda))
;; get info about a built-in form
g > (get-info asm-file)
[Built-in Form] asm-file
```
## `autocomplete`
Preview the results of the REPL autocomplete:
```lisp
(autcomplete <sym>)
```
For example:
```lisp
g > (autocomplete *)
*
*16k-dead-pool*
*4k-dead-pool*
...
Autocomplete: 326/1474 symbols matched, took 1.29 ms
```
## `seval`
Execute GOOS code.
```lisp
+6 -1
View File
@@ -15,9 +15,14 @@ Compiler::Compiler() : m_debugger(&m_listener) {
m_global_env = std::make_unique<GlobalEnv>();
m_none = std::make_unique<None>(m_ts.make_typespec("none"));
// todo - compile library
// compile GOAL library
Object library_code = m_goos.reader.read_from_file({"goal_src", "goal-lib.gc"});
compile_object_file("goal-lib", library_code, false);
// add built-in forms to symbol info
for (auto& builtin : g_goal_forms) {
m_symbol_info.add_builtin(builtin.first);
}
}
ReplStatus Compiler::execute_repl() {
+12
View File
@@ -16,6 +16,7 @@
#include "third-party/fmt/core.h"
#include "third-party/fmt/color.h"
#include "CompilerException.h"
#include "goalc/compiler/SymbolInfo.h"
#include "Enum.h"
enum MathMode { MATH_INT, MATH_BINT, MATH_FLOAT, MATH_INVALID };
@@ -185,6 +186,8 @@ class Compiler {
int offset,
Env* env);
std::string make_symbol_info_description(const SymbolInfo& info);
TypeSystem m_ts;
std::unique_ptr<GlobalEnv> m_global_env = nullptr;
std::unique_ptr<None> m_none = nullptr;
@@ -199,6 +202,8 @@ class Compiler {
std::unordered_map<std::shared_ptr<goos::SymbolObject>, LambdaVal*> m_inlineable_functions;
CompilerSettings m_settings;
bool m_throw_on_define_extern_redefinition = false;
SymbolInfoMap m_symbol_info;
MathMode get_math_mode(const TypeSpec& ts);
bool is_number(const TypeSpec& ts);
bool is_float(const TypeSpec& ts);
@@ -427,6 +432,8 @@ class Compiler {
Val* compile_in_package(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_build_dgo(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_reload(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_get_info(const goos::Object& form, const goos::Object& rest, Env* env);
Val* compile_autocomplete(const goos::Object& form, const goos::Object& rest, Env* env);
// ControlFlow
Condition compile_condition(const goos::Object& condition, Env* env, bool invert);
@@ -503,4 +510,9 @@ class Compiler {
Val* compile_defenum(const goos::Object& form, const goos::Object& rest, Env* env);
};
extern const std::unordered_map<
std::string,
Val* (Compiler::*)(const goos::Object& form, const goos::Object& rest, Env* env)>
g_goal_forms;
#endif // JAK_COMPILER_H
+170
View File
@@ -0,0 +1,170 @@
#pragma once
#include <cassert>
#include <vector>
#include <string>
#include <set>
#include "common/util/Trie.h"
#include "common/goos/Object.h"
/*!
* Info about a single symbol, representing one of:
* - Global variable
* - Global function
* - Type
* - Constant
* - Macro
* - Builtin keyword of the OpenGOAL language
*/
class SymbolInfo {
public:
enum class Kind {
GLOBAL_VAR,
FWD_DECLARED_SYM,
FUNCTION,
TYPE,
CONSTANT,
MACRO,
LANGUAGE_BUILTIN,
METHOD,
INVALID
};
static SymbolInfo make_global(const std::string& name, const goos::Object& defining_form) {
SymbolInfo info;
info.m_kind = Kind::GLOBAL_VAR;
info.m_name = name;
info.m_def_form = defining_form;
return info;
}
static SymbolInfo make_fwd_declared_sym(const std::string& name,
const goos::Object& defining_form) {
SymbolInfo info;
info.m_kind = Kind::FWD_DECLARED_SYM;
info.m_name = name;
info.m_def_form = defining_form;
return info;
}
static SymbolInfo make_function(const std::string& name, const goos::Object& defining_form) {
SymbolInfo info;
info.m_kind = Kind::FUNCTION;
info.m_name = name;
info.m_def_form = defining_form;
return info;
}
static SymbolInfo make_type(const std::string& name, const goos::Object& defining_form) {
SymbolInfo info;
info.m_kind = Kind::TYPE;
info.m_name = name;
info.m_def_form = defining_form;
return info;
}
static SymbolInfo make_constant(const std::string& name, const goos::Object& defining_form) {
SymbolInfo info;
info.m_kind = Kind::CONSTANT;
info.m_name = name;
info.m_def_form = defining_form;
return info;
}
static SymbolInfo make_macro(const std::string& name, const goos::Object& defining_form) {
SymbolInfo info;
info.m_kind = Kind::MACRO;
info.m_name = name;
info.m_def_form = defining_form;
return info;
}
static SymbolInfo make_builtin(const std::string& name) {
SymbolInfo info;
info.m_kind = Kind::LANGUAGE_BUILTIN;
info.m_name = name;
return info;
}
static SymbolInfo make_method(const std::string& method_name,
const std::string& type_name,
const goos::Object& defining_form) {
SymbolInfo info;
info.m_kind = Kind::METHOD;
info.m_name = method_name;
info.m_type_of_method = type_name;
info.m_def_form = defining_form;
return info;
}
const std::string& name() const { return m_name; }
const std::string& type() const { return m_type_of_method; }
Kind kind() const { return m_kind; }
const goos::Object& src_form() const { return m_def_form; }
private:
Kind m_kind = Kind::INVALID;
goos::Object m_def_form;
std::string m_name;
std::string m_type_of_method;
};
/*!
* A map of symbol info. It internally stores the info in a prefix tree so you can quickly get
* a list of all symbols starting with a given prefix.
*/
class SymbolInfoMap {
public:
SymbolInfoMap() = default;
void add_global(const std::string& name, const goos::Object& defining_form) {
m_map[name]->push_back(SymbolInfo::make_global(name, defining_form));
}
void add_fwd_dec(const std::string& name, const goos::Object& defining_form) {
m_map[name]->push_back(SymbolInfo::make_fwd_declared_sym(name, defining_form));
}
void add_function(const std::string& name, const goos::Object& defining_form) {
m_map[name]->push_back(SymbolInfo::make_function(name, defining_form));
}
void add_type(const std::string& name, const goos::Object& defining_form) {
m_map[name]->push_back(SymbolInfo::make_type(name, defining_form));
}
void add_constant(const std::string& name, const goos::Object& defining_form) {
m_map[name]->push_back(SymbolInfo::make_constant(name, defining_form));
}
void add_macro(const std::string& name, const goos::Object& defining_form) {
m_map[name]->push_back(SymbolInfo::make_macro(name, defining_form));
}
void add_builtin(const std::string& name) {
m_map[name]->push_back(SymbolInfo::make_builtin(name));
}
void add_method(const std::string& method_name,
const std::string& type_name,
const goos::Object& defining_form) {
m_map[method_name]->push_back(SymbolInfo::make_method(method_name, type_name, defining_form));
}
std::vector<SymbolInfo>* lookup_exact_name(const std::string& name) { return m_map.lookup(name); }
std::set<std::string> lookup_symbols_starting_with(const std::string& prefix) {
std::set<std::string> result;
auto lookup = m_map.lookup_prefix(prefix);
for (auto& x : lookup) {
for (auto& y : *x) {
result.insert(y.name());
}
}
return result;
}
int symbol_count() const { return m_map.size(); }
private:
Trie<std::vector<SymbolInfo>> m_map;
};
+6 -4
View File
@@ -9,10 +9,10 @@
/*!
* Main table for compiler forms
*/
static const std::unordered_map<
const std::unordered_map<
std::string,
Val* (Compiler::*)(const goos::Object& form, const goos::Object& rest, Env* env)>
goal_forms = {
g_goal_forms = {
// INLINE ASM
{".nop", &Compiler::compile_nop},
{".ret", &Compiler::compile_asm_ret},
@@ -114,6 +114,8 @@ static const std::unordered_map<
{":status", &Compiler::compile_poke},
{"in-package", &Compiler::compile_in_package},
{"reload", &Compiler::compile_reload},
{"get-info", &Compiler::compile_get_info},
{"autocomplete", &Compiler::compile_autocomplete},
// CONDITIONAL COMPILATION
{"#cond", &Compiler::compile_gscond},
@@ -247,8 +249,8 @@ Val* Compiler::compile_pair(const goos::Object& code, Env* env) {
if (head.is_symbol()) {
auto head_sym = head.as_symbol();
// first try as a goal compiler form
auto kv_gfs = goal_forms.find(head_sym->name);
if (kv_gfs != goal_forms.end()) {
auto kv_gfs = g_goal_forms.find(head_sym->name);
if (kv_gfs != g_goal_forms.end()) {
return ((*this).*(kv_gfs->second))(code, rest, env);
}
@@ -391,3 +391,72 @@ Val* Compiler::compile_reload(const goos::Object& form, const goos::Object& rest
m_want_reload = true;
return get_none();
}
std::string Compiler::make_symbol_info_description(const SymbolInfo& info) {
switch (info.kind()) {
case SymbolInfo::Kind::GLOBAL_VAR:
return fmt::format("[Global Variable] Type: {} Defined: {}",
m_symbol_types.at(info.name()).print(),
m_goos.reader.db.get_info_for(info.src_form()));
case SymbolInfo::Kind::LANGUAGE_BUILTIN:
return fmt::format("[Built-in Form] {}\n", info.name());
case SymbolInfo::Kind::METHOD:
return fmt::format("[Method] Type: {} Method Name: {} Defined: {}", info.type(), info.name(),
m_goos.reader.db.get_info_for(info.src_form()));
case SymbolInfo::Kind::TYPE:
return fmt::format("[Type] Name: {} Defined: {}", info.name(),
m_goos.reader.db.get_info_for(info.src_form()));
case SymbolInfo::Kind::MACRO:
return fmt::format("[Macro] Name: {} Defined: {}", info.name(),
m_goos.reader.db.get_info_for(info.src_form()));
case SymbolInfo::Kind::CONSTANT:
return fmt::format(
"[Constant] Name: {} Value: {} Defined: {}", info.name(),
m_global_constants.at(m_goos.reader.symbolTable.intern(info.name())).print(),
m_goos.reader.db.get_info_for(info.src_form()));
case SymbolInfo::Kind::FUNCTION:
return fmt::format("[Function] Name: {} Defined: {}", info.name(),
m_goos.reader.db.get_info_for(info.src_form()));
case SymbolInfo::Kind::FWD_DECLARED_SYM:
return fmt::format("[Forward-Declared] Name: {} Defined: {}", info.name(),
m_goos.reader.db.get_info_for(info.src_form()));
default:
assert(false);
}
}
Val* Compiler::compile_get_info(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
va_check(form, args, {goos::ObjectType::SYMBOL}, {});
auto result = m_symbol_info.lookup_exact_name(args.unnamed.at(0).as_symbol()->name);
if (!result) {
fmt::print("No results found.\n");
} else {
for (auto& info : *result) {
fmt::print("{}", make_symbol_info_description(info));
}
}
return get_none();
}
Val* Compiler::compile_autocomplete(const goos::Object& form, const goos::Object& rest, Env* env) {
(void)env;
auto args = get_va(form, rest);
va_check(form, args, {goos::ObjectType::SYMBOL}, {});
Timer timer;
auto result = m_symbol_info.lookup_symbols_starting_with(args.unnamed.at(0).as_symbol()->name);
auto time = timer.getMs();
for (auto& x : result) {
fmt::print(" {}\n", x);
}
fmt::print("Autocomplete: {}/{} symbols matched, took {:.2f} ms\n", result.size(),
m_symbol_info.symbol_count(), time);
return get_none();
}
+8 -3
View File
@@ -37,6 +37,11 @@ Val* Compiler::compile_define(const goos::Object& form, const goos::Object& rest
if ((as_lambda->func && as_lambda->func->settings.allow_inline) || !as_lambda->func) {
m_inlineable_functions[sym.as_symbol()] = as_lambda;
}
m_symbol_info.add_function(symbol_string(sym), form);
}
if (!sym_val->settable()) {
throw_compiler_error(form, "Cannot define {} because it cannot be set.", sym_val->print());
}
auto in_gpr = compiled_val->to_gpr(fe);
@@ -53,9 +58,8 @@ Val* Compiler::compile_define(const goos::Object& form, const goos::Object& rest
}
}
if (!sym_val->settable()) {
throw_compiler_error(form, "Cannot define {} because it cannot be set.", sym_val->print());
}
m_symbol_info.add_global(symbol_string(sym), form);
fe->emit(std::make_unique<IR_SetSymbolValue>(sym_val, in_gpr));
return in_gpr;
}
@@ -91,6 +95,7 @@ Val* Compiler::compile_define_extern(const goos::Object& form, const goos::Objec
}
m_symbol_types[symbol_string(sym)] = new_type;
m_symbol_info.add_fwd_dec(symbol_string(sym), form);
return get_none();
}
+4
View File
@@ -36,6 +36,8 @@ Val* Compiler::compile_goos_macro(const goos::Object& o,
m_goos.goal_to_goos.enclosing_method_type =
get_parent_env_of_type<FunctionEnv>(env)->method_of_type_name;
auto goos_result = m_goos.eval_list_return_last(macro->body, macro->body, mac_env);
// make the macro expanded form point to the source where the macro was used for error messages.
m_goos.reader.db.inherit_info(o, goos_result);
m_goos.goal_to_goos.reset();
return compile_error_guard(goos_result, env);
}
@@ -146,6 +148,8 @@ Val* Compiler::compile_define_constant(const goos::Object& form,
m_goos.global_environment.as_env()->vars[sym] = value;
}
m_symbol_info.add_constant(sym->name, form);
return get_none();
}
+4
View File
@@ -251,6 +251,8 @@ Val* Compiler::compile_deftype(const goos::Object& form, const goos::Object& res
// Auto-generate (inspect) method
generate_inspector_for_type(form, env, result.type_info);
m_symbol_info.add_type(result.type.base_type(), form);
// return none, making the value of (deftype..) unusable
return get_none();
}
@@ -382,6 +384,8 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
}
place->set_type(lambda_ts);
m_symbol_info.add_method(symbol_string(method_name), symbol_string(type_name), form);
auto info =
m_ts.add_method(symbol_string(type_name), symbol_string(method_name), lambda_ts, false);
auto type_obj = compile_get_symbol_value(form, symbol_string(type_name), env)->to_gpr(env);
+30 -1
View File
@@ -1,12 +1,41 @@
#include "common/util/FileUtil.h"
#include "common/util/Trie.h"
#include "gtest/gtest.h"
#include "test/all_jak1_symbols.h"
#include <string>
#include <vector>
TEST(FileUtil, valid_path) {
TEST(CommonUtil, get_file_path) {
std::vector<std::string> test = {"cabbage", "banana", "apple"};
std::string sampleString = file_util::get_file_path(test);
// std::cout << sampleString << std::endl;
EXPECT_TRUE(true);
}
TEST(CommonUtil, Trie) {
Trie<std::string> test;
std::vector<std::string> strings;
for (auto x : all_syms) {
strings.push_back(x);
test.insert(strings.back(), strings.back());
}
auto cam_prefix = test.lookup_prefix("cam");
EXPECT_EQ(cam_prefix.size(), 184);
EXPECT_EQ(test.lookup("not-in-the-list"), nullptr);
EXPECT_EQ(test.lookup("cam"), nullptr);
EXPECT_NE(test.lookup("energydoor-closed-till-near"), nullptr);
EXPECT_EQ(7941, test.lookup_prefix("").size());
EXPECT_TRUE(test.lookup("") == nullptr);
EXPECT_TRUE(test.lookup("p") == nullptr);
EXPECT_TRUE(test.lookup("pa") == nullptr);
EXPECT_TRUE(test.lookup("pat") == nullptr);
EXPECT_FALSE(test.lookup("path") == nullptr);
EXPECT_FALSE(test.lookup("path1") == nullptr);
EXPECT_TRUE(test.lookup("path-") == nullptr);
EXPECT_FALSE(test.lookup("path1-k") == nullptr);
}