1 // Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info.
2
3 #include "database.h"
4
5 #include <memory>
6 #include <unordered_map>
7 #include <queue>
8
9 #include "c3.h"
10 #include "compiler.h"
11 #include "error.h"
12 #include "file.h"
13 #include "namespace.h"
14 #include "object_state.h"
15 #include "parser.h"
16 #include "patch_info.h"
17 #include "state.h"
18 #include "util.h"
19 #include "view.h"
20
21
22 namespace nyan {
23
create()24 std::shared_ptr<Database> Database::create() {
25 return std::make_shared<Database>();
26 }
27
28
Database()29 Database::Database()
30 :
31 state{std::make_shared<State>()} {}
32
33
34 Database::~Database() = default;
35
36
37 /**
38 * Called for each object.
39 * Contains the scope, current namespace,
40 * name of the object, and the astobject.
41 */
42 using ast_objwalk_cb_t = std::function<void(const NamespaceFinder &,
43 const Namespace &,
44 const Namespace &,
45 const ASTObject &)>;
46
47
ast_obj_walk_recurser(const ast_objwalk_cb_t & callback,const NamespaceFinder & scope,const Namespace & ns,const std::vector<ASTObject> & objs)48 static void ast_obj_walk_recurser(const ast_objwalk_cb_t &callback,
49 const NamespaceFinder &scope,
50 const Namespace &ns,
51 const std::vector<ASTObject> &objs) {
52
53 // go over all objects
54 for (auto &astobj : objs) {
55 Namespace objname{ns, astobj.get_name().get()};
56
57 // process nested objects first
58 ast_obj_walk_recurser(callback, scope, objname, astobj.get_objects());
59
60 // do whatever needed
61 callback(scope, ns, objname, astobj);
62 }
63 }
64
65
ast_obj_walk(const namespace_lookup_t & imports,const ast_objwalk_cb_t & cb)66 static void ast_obj_walk(const namespace_lookup_t &imports,
67 const ast_objwalk_cb_t &cb) {
68
69 // go over all the imported files
70 for (auto &it : imports) {
71 const Namespace &ns = it.first;
72 const NamespaceFinder ¤t_file = it.second;
73 const AST &ast = current_file.get_ast();
74
75 // each file has many objects, which can be nested.
76 ast_obj_walk_recurser(cb, current_file, ns, ast.get_objects());
77 }
78 }
79
80
load(const std::string & filename,const filefetcher_t & filefetcher)81 void Database::load(const std::string &filename,
82 const filefetcher_t &filefetcher) {
83
84 Parser parser;
85
86 // tracking of imported namespaces (with aliases)
87 namespace_lookup_t imports;
88
89 // namespaces to which were requested to be imported
90 // the location is the first request origin.
91 std::unordered_map<Namespace, Location> to_import;
92
93 // push the first namespace to import
94 to_import.insert(
95 {
96 Namespace::from_filename(filename),
97 Location{"explicit load request"}
98 }
99 );
100
101 while (to_import.size() > 0) {
102 auto cur_ns_it = to_import.begin();
103 const Namespace &namespace_to_import = cur_ns_it->first;
104 const Location &req_location = cur_ns_it->second;
105
106 auto it = imports.find(namespace_to_import);
107 if (it != std::end(imports)) {
108 // this namespace is already imported!
109 continue;
110 }
111
112 std::shared_ptr<File> current_file;
113 try {
114 // get the data and parse the file
115 current_file = filefetcher(
116 namespace_to_import.to_filename()
117 );
118 }
119 catch (FileReadError &err) {
120 // the import request failed,
121 // so the nyan file structure or content is wrong.
122 throw FileError{req_location, err.str()};
123 }
124
125 // create import tracking entry for this file
126 // and parse the file contents!
127 NamespaceFinder &new_ns = imports.insert({
128 namespace_to_import, // name of the import
129 NamespaceFinder{
130 parser.parse(current_file) // read the ast!
131 }
132 }).first->second;
133
134 // enqueue all new imports of this file
135 // and record import aliases
136 for (auto &import : new_ns.get_ast().get_imports()) {
137 Namespace request{import.get()};
138
139 // either register the alias
140 if (import.has_alias()) {
141 new_ns.add_alias(import.get_alias(), request);
142 }
143 // or the plain import
144 else {
145 new_ns.add_import(request);
146 }
147
148 // check if this import was already requested or is known.
149 // todo: also check if that ns is already fully loaded in the db
150 auto was_imported = imports.find(request);
151 auto import_requested = to_import.find(request);
152
153 if (was_imported == std::end(imports) and
154 import_requested == std::end(to_import)) {
155
156 // add the request to the pending imports
157 to_import.insert({std::move(request), import.get()});
158 }
159 }
160
161 to_import.erase(cur_ns_it);
162 }
163
164
165 using namespace std::placeholders;
166
167 size_t new_obj_count = 0;
168
169 // first run: create empty object info objects
170 ast_obj_walk(imports, std::bind(&Database::create_obj_info,
171 this, &new_obj_count,
172 _1, _2, _3, _4));
173
174 std::vector<fqon_t> new_objects;
175 new_objects.reserve(new_obj_count);
176
177 // map object => new children.
178 std::unordered_map<fqon_t, std::unordered_set<fqon_t>> obj_children;
179
180 // now, all new object infos need to be filled with types
181 ast_obj_walk(imports, std::bind(&Database::create_obj_content,
182 this,
183 &new_objects,
184 &obj_children,
185 _1, _2, _3, _4));
186
187 // linearize the parents of all new objects
188 this->linearize_new(new_objects);
189
190 // resolve the types of members to their definition
191 this->resolve_types(new_objects);
192
193 // these objects were uses as values at some file location.
194 std::vector<std::pair<fqon_t, Location>> objs_in_values;
195
196 // state value creation
197 ast_obj_walk(imports, std::bind(&Database::create_obj_state,
198 this, &objs_in_values,
199 _1, _2, _3, _4));
200
201 // verify hierarchy consistency
202 this->check_hierarchy(new_objects, objs_in_values);
203
204 // store the children mapping.
205 for (auto &it : obj_children) {
206 auto &obj = it.first;
207 auto &children = it.second;
208
209 ObjectInfo *info = this->meta_info.get_object(obj);
210 if (unlikely(info == nullptr)) {
211 throw InternalError{"object info could not be retrieved"};
212 }
213
214 info->set_children(std::move(children));
215 }
216
217 // TODO: check pending objectvalues (probably not needed as they're all loaded)
218 }
219
220
create_obj_info(size_t * counter,const NamespaceFinder & current_file,const Namespace &,const Namespace & objname,const ASTObject & astobj)221 void Database::create_obj_info(size_t *counter,
222 const NamespaceFinder ¤t_file,
223 const Namespace &,
224 const Namespace &objname,
225 const ASTObject &astobj) {
226
227 const std::string &name = astobj.name.get();
228
229 // object name must not be an alias
230 if (current_file.check_conflict(name)) {
231 // TODO: show conflict origin (the import)
232 throw NameError{
233 astobj.name,
234 "object name conflicts with import",
235 name
236 };
237 }
238
239 this->meta_info.add_object(
240 objname.to_fqon(),
241 ObjectInfo{astobj.name}
242 );
243
244 *counter += 1;
245 }
246
247
create_obj_content(std::vector<fqon_t> * new_objs,std::unordered_map<fqon_t,std::unordered_set<fqon_t>> * child_assignments,const NamespaceFinder & scope,const Namespace & ns,const Namespace & objname,const ASTObject & astobj)248 void Database::create_obj_content(std::vector<fqon_t> *new_objs,
249 std::unordered_map<fqon_t, std::unordered_set<fqon_t>> *child_assignments,
250 const NamespaceFinder &scope,
251 const Namespace &ns,
252 const Namespace &objname,
253 const ASTObject &astobj) {
254
255 fqon_t obj_fqon = objname.to_fqon();
256 new_objs->push_back(obj_fqon);
257
258 ObjectInfo *info = this->meta_info.get_object(obj_fqon);
259 if (unlikely(info == nullptr)) {
260 throw InternalError{"object info could not be retrieved"};
261 }
262
263 // save the patch target, has to be alias-expanded
264 const IDToken &target = astobj.target;
265 if (target.exists()) {
266 fqon_t target_id = scope.find(ns, target, this->meta_info);
267 info->add_patch(std::make_shared<PatchInfo>(std::move(target_id)), true);
268 }
269
270 // a patch may add inheritance parents
271 for (auto &change : astobj.inheritance_change) {
272 inher_change_t change_type = change.get_type();
273 fqon_t new_parent_id = scope.find(ns, change.get_target(), this->meta_info);
274 info->add_inheritance_change(InheritanceChange{change_type, std::move(new_parent_id)});
275 }
276
277 // parents are stored in the object data state
278 std::deque<fqon_t> object_parents;
279 for (auto &parent : astobj.parents) {
280 fqon_t parent_id = scope.find(ns, parent, this->meta_info);
281
282 // this object is therefore a child of the parent one.
283 auto ins = child_assignments->emplace(
284 parent_id,
285 std::unordered_set<fqon_t>{}
286 );
287 ins.first->second.insert(obj_fqon);
288
289 object_parents.push_back(std::move(parent_id));
290 }
291
292 // fill initial state:
293 this->state->add_object(
294 obj_fqon,
295 std::make_shared<ObjectState>(
296 std::move(object_parents)
297 )
298 );
299
300 // create member types
301 for (auto &astmember : astobj.members) {
302
303 // TODO: the member name requires advanced expansions
304 // for conflict resolving
305
306 MemberInfo &member_info = info->add_member(
307 astmember.name.str(),
308 MemberInfo{astmember.name}
309 );
310
311 if (not astmember.type.exists()) {
312 continue;
313 }
314
315 // if existing, create type information of member.
316 member_info.set_type(
317 std::make_shared<Type>(
318 astmember.type,
319 scope,
320 ns,
321 this->meta_info
322 ),
323 true // type was defined in the ast -> initial definition
324 );
325 }
326 }
327
328
linearize_new(const std::vector<fqon_t> & new_objects)329 void Database::linearize_new(const std::vector<fqon_t> &new_objects) {
330 // linearize the parents of all newly created objects
331
332 for (auto &obj : new_objects) {
333 std::unordered_set<fqon_t> seen;
334
335 ObjectInfo *obj_info = this->meta_info.get_object(obj);
336 if (unlikely(obj_info == nullptr)) {
337 throw InternalError{"object information not retrieved"};
338 }
339
340 obj_info->set_linearization(
341 linearize_recurse(
342 obj,
343 [this] (const fqon_t &name) -> const ObjectState & {
344 return **this->state->get(name);
345 },
346 &seen
347 )
348 );
349 }
350 }
351
352
find_member(bool skip_first,const memberid_t & member_id,const std::vector<fqon_t> & search_objs,const ObjectInfo & obj_info,const std::function<bool (const fqon_t &,const MemberInfo &,const Member *)> & member_found)353 void Database::find_member(bool skip_first,
354 const memberid_t &member_id,
355 const std::vector<fqon_t> &search_objs,
356 const ObjectInfo &obj_info,
357 const std::function<bool(const fqon_t &,
358 const MemberInfo &,
359 const Member *)> &member_found) {
360
361 bool finished = false;
362
363 // member doesn't have type yet. find it.
364 for (auto &obj : search_objs) {
365
366 // at the very beginning, we have to skip the object
367 // we want to find the type for. it's the first in the linearization.
368 if (skip_first) {
369 skip_first = false;
370 continue;
371 }
372
373 ObjectInfo *obj_info = this->meta_info.get_object(obj);
374 if (unlikely(obj_info == nullptr)) {
375 throw InternalError{"object information not retrieved"};
376 }
377 const MemberInfo *obj_member_info = obj_info->get_member(member_id);
378
379 // obj doesn't have this member
380 if (not obj_member_info) {
381 continue;
382 }
383
384 const ObjectState *par_state = this->state->get(obj)->get();
385 if (unlikely(par_state == nullptr)) {
386 throw InternalError{"object state not retrieved"};
387 }
388 const Member *member = par_state->get_member(member_id);
389
390 finished = member_found(obj, *obj_member_info, member);
391
392 if (finished) {
393 break;
394 }
395 }
396
397 // recurse into the patch target
398 if (not finished and obj_info.is_patch()) {
399 const fqon_t &target = obj_info.get_patch()->get_target();
400 const ObjectInfo *obj_info = this->meta_info.get_object(target);
401 if (unlikely(obj_info == nullptr)) {
402 throw InternalError{"target not found in metainfo"};
403 }
404
405 // recurse into the target.
406 // check if the patch defines the member as well -> error.
407 // otherwise, infer type from patch.
408 this->find_member(false, member_id,
409 obj_info->get_linearization(),
410 *obj_info, member_found);
411 }
412 }
413
414
resolve_types(const std::vector<fqon_t> & new_objects)415 void Database::resolve_types(const std::vector<fqon_t> &new_objects) {
416
417 using namespace std::string_literals;
418
419 // TODO: if inheritance parents are added,
420 // should a patch be able to modify the newly accessible members?
421
422 // link patch information to the origin patch
423 // and check if there's not multiple patche targets per object hierarchy
424 for (auto &obj : new_objects) {
425 ObjectInfo *obj_info = this->meta_info.get_object(obj);
426
427 const auto &linearization = obj_info->get_linearization();
428 if (unlikely(linearization.size() < 1)) {
429 throw InternalError{
430 "Linearization doesn't contain obj itself."
431 };
432 }
433
434 auto it = std::begin(linearization);
435 // skip first, it's the object itself.
436 ++it;
437 for (auto end = std::end(linearization); it != end; ++it) {
438 ObjectInfo *parent_info = this->meta_info.get_object(*it);
439
440 if (parent_info->is_initial_patch()) {
441 if (unlikely(obj_info->is_initial_patch())) {
442 // TODO: show patch target instead of member
443 throw ReasonError{
444 obj_info->get_location(),
445 "child patches can't declare a patch target",
446 {{parent_info->get_location(), "parent that declares the patch"}}
447 };
448 }
449 else {
450 // this is patch because of inheritance.
451 // false => it wasn't initially a patch.
452 obj_info->add_patch(parent_info->get_patch(), false);
453 }
454 }
455 }
456 }
457
458 // resolve member types:
459 // link member types to matching parent if not known yet.
460 // this required that patch targets are linked.
461 for (auto &obj : new_objects) {
462 ObjectInfo *obj_info = this->meta_info.get_object(obj);
463
464 const auto &linearization = obj_info->get_linearization();
465
466 // resolve the type for each member
467 for (auto &it : obj_info->get_members()) {
468 const memberid_t &member_id = it.first;
469 MemberInfo &member_info = it.second;
470
471 // if the member already defines it, we found it already.
472 // we still need to check for conflicts though.
473 bool type_found = member_info.is_initial_def();
474
475 // start the recursion into the inheritance tree,
476 // which includes the recursion into patch targets.
477 this->find_member(
478 true, // make sure the object we search the type for isn't checked with itself.
479 member_id, linearization, *obj_info,
480 [&member_info, &type_found, &member_id]
481 (const fqon_t &parent,
482 const MemberInfo &source_member_info,
483 const Member *) {
484
485 if (source_member_info.is_initial_def()) {
486 const std::shared_ptr<Type> &new_type = source_member_info.get_type();
487
488 if (unlikely(not new_type.get())) {
489 throw InternalError{"initial type definition has no type"};
490 }
491
492 // check if the member we're looking for isn't already typed.
493 if (unlikely(member_info.is_initial_def())) {
494 // another parent defines this type,
495 // which is disallowed!
496
497 // TODO: show location of infringing type instead of member
498 throw ReasonError{
499 member_info.get_location(),
500 ("parent '"s + parent
501 + "' already defines type of '" + member_id + "'"),
502 {{source_member_info.get_location(), "parent that declares the member"}}
503 };
504 }
505
506 type_found = true;
507 member_info.set_type(new_type, false);
508 }
509 // else that member knows the type,
510 // but we're looking for the initial definition.
511
512 // we need to traverse all members and never stop early.
513 return false;
514 }
515 );
516
517 if (unlikely(not type_found)) {
518 throw TypeError{
519 member_info.get_location(),
520 "could not infer type of '"s + member_id
521 + "' from parents or patch target"
522 };
523 }
524 }
525 }
526 }
527
528
create_obj_state(std::vector<std::pair<fqon_t,Location>> * objs_in_values,const NamespaceFinder & scope,const Namespace &,const Namespace & objname,const ASTObject & astobj)529 void Database::create_obj_state(std::vector<std::pair<fqon_t, Location>> *objs_in_values,
530 const NamespaceFinder &scope,
531 const Namespace &,
532 const Namespace &objname,
533 const ASTObject &astobj) {
534
535 using namespace std::string_literals;
536
537 if (astobj.members.size() == 0) {
538 // no members, nothing to do.
539 return;
540 }
541
542 ObjectInfo *info = this->meta_info.get_object(objname.to_fqon());
543 if (unlikely(info == nullptr)) {
544 throw InternalError{"object info could not be retrieved"};
545 }
546
547 ObjectState &objstate = **this->state->get(objname.to_fqon());
548
549 std::unordered_map<memberid_t, Member> members;
550
551 // create member values
552 for (auto &astmember : astobj.members) {
553
554 // member has no value
555 if (not astmember.value.exists()) {
556 continue;
557 }
558
559 // TODO: the member name may need some resolution for conflicts
560 const memberid_t &memberid = astmember.name.str();
561
562 const MemberInfo *member_info = info->get_member(memberid);
563 if (unlikely(member_info == nullptr)) {
564 throw InternalError{"member info could not be retrieved"};
565 }
566
567 const Type *member_type = member_info->get_type().get();
568 if (unlikely(member_type == nullptr)) {
569 throw InternalError{"member type could not be retrieved"};
570 }
571
572 nyan_op operation = astmember.operation;
573
574 if (unlikely(operation == nyan_op::INVALID)) {
575 // the ast buildup should have ensured this.
576 throw InternalError{"member has value but no operator"};
577 }
578
579 // create the member with operation and value
580 Member &new_member = members.emplace(
581 memberid,
582 Member{
583 0, // TODO: get override depth from AST (the @-count)
584 operation,
585 Value::from_ast(
586 *member_type, astmember.value,
587 // function to determine object names used in values:
588 [&scope, &objname, this, &objs_in_values]
589 (const Type &target_type,
590 const IDToken &token) -> fqon_t {
591
592 // find the desired object in the scope of the object
593 fqon_t obj_id = scope.find(objname, token, this->meta_info);
594
595 ObjectInfo *obj_info = this->meta_info.get_object(obj_id);
596 if (unlikely(obj_info == nullptr)) {
597 throw InternalError{"object info could not be retrieved"};
598 }
599
600 const auto &obj_lin = obj_info->get_linearization();
601
602 // check if the type of the value is okay
603 // (i.e. it's in the linearization)
604 if (unlikely(not util::contains(obj_lin, target_type.get_target()))) {
605
606 throw TypeError{
607 token,
608 "value (resolved as "s + obj_id
609 + ") does not match type " + target_type.get_target()
610 };
611 }
612
613 // remember to check if this object can be used as value
614 objs_in_values->push_back({obj_id, Location{token}});
615
616 return obj_id;
617 }
618 )
619 }
620 ).first->second;
621
622 // let the value determine if it can work together
623 // with the member type.
624 const Value &new_value = new_member.get_value();
625 const std::unordered_set<nyan_op> &allowed_ops = new_value.allowed_operations(*member_type);
626
627 if (unlikely(allowed_ops.find(operation) == std::end(allowed_ops))) {
628 // TODO: show location of operation, not the member name
629
630 // I'm really sorry for this flower meadow of an error message.
631 throw TypeError{
632 astmember.name,
633 "invalid operator "s
634 + op_to_string(operation)
635 + ": member type "s
636 + member_type->str()
637 +
638 (allowed_ops.size() > 0
639 ?
640 " only allows operations '"s
641 + util::strjoin(
642 ", ", allowed_ops,
643 [] (const nyan_op &op) {
644 return op_to_string(op);
645 }
646 )
647 + "' "
648 :
649 " allows no operations "
650 )
651 + "for value "
652 + new_value.str()
653 };
654 }
655 }
656
657 objstate.set_members(std::move(members));
658 }
659
660
check_hierarchy(const std::vector<fqon_t> & new_objs,const std::vector<std::pair<fqon_t,Location>> & objs_in_values)661 void Database::check_hierarchy(const std::vector<fqon_t> &new_objs,
662 const std::vector<std::pair<fqon_t, Location>> &objs_in_values) {
663 using namespace std::string_literals;
664
665 for (auto &obj : new_objs) {
666
667 ObjectInfo *obj_info = this->meta_info.get_object(obj);
668 ObjectState *obj_state = this->state->get(obj)->get();
669 if (unlikely(obj_info == nullptr)) {
670 throw InternalError{"object info could not be retrieved"};
671 }
672 if (unlikely(obj_state == nullptr)) {
673 throw InternalError{"initial object state could not be retrieved"};
674 }
675
676 // check if an object has inher parent adds, it must be a patch.
677 if (obj_info->get_inheritance_change().size() > 0) {
678 if (unlikely(not obj_info->is_patch())) {
679 throw FileError{
680 obj_info->get_location(),
681 "Inheritance additions can only be done in patches."
682 };
683 }
684 }
685
686 const auto &linearization = obj_info->get_linearization();
687
688 // check that relative operators can't be performed when the parent has no value.
689 for (auto &it : obj_state->get_members()) {
690 bool assign_ok = false;
691 bool other_op = false;
692
693 this->find_member(
694 false, it.first, linearization, *obj_info,
695 [&assign_ok, &other_op]
696 (const fqon_t &,
697 const MemberInfo &,
698 const Member *member) {
699 // member has no value
700 if (member == nullptr) {
701 return false;
702 }
703
704 nyan_op op = member->get_operation();
705 if (op == nyan_op::ASSIGN) {
706 assign_ok = true;
707 return true;
708 }
709 else if (likely(op != nyan_op::INVALID)) {
710 other_op = true;
711 }
712 else {
713 // op == INVALID
714 throw InternalError{"member has invalid operator"};
715 }
716 return false;
717 }
718 );
719
720 if (unlikely(other_op and not assign_ok)) {
721 const MemberInfo *member_info = obj_info->get_member(it.first);
722 throw FileError{
723 member_info->get_location(),
724 "this member was never assigned a value."
725 };
726 }
727 }
728
729 // TODO: check the @-propagation is type-compatible for each operator
730 // -> can we even know? yes, as the patch target depth must be >= @-count.
731 }
732
733
734 std::unordered_set<fqon_t> obj_values_ok;
735
736 for (auto &it : objs_in_values) {
737 const fqon_t &obj_id = it.first;
738
739 if (obj_values_ok.find(obj_id) != std::end(obj_values_ok)) {
740 // the object is non-abstract.
741 continue;
742 }
743
744 ObjectInfo *obj_info = this->meta_info.get_object(obj_id);
745 if (unlikely(obj_info == nullptr)) {
746 throw InternalError{"object info could not be retrieved"};
747 }
748
749 const auto &lin = obj_info->get_linearization();
750 std::unordered_set<fqon_t> pending_members;
751
752 for (auto obj = std::rbegin(lin); obj != std::rend(lin); ++obj) {
753 const ObjectInfo *obj_info = this->meta_info.get_object(*obj);
754 if (unlikely(obj_info == nullptr)) {
755 throw InternalError{"object used as value has no metainfo"};
756 }
757 const ObjectState *obj_state = this->state->get(*obj)->get();
758 if (unlikely(obj_state == nullptr)) {
759 throw InternalError{"object in hierarchy has no state"};
760 }
761
762 const auto &state_members = obj_state->get_members();
763
764 // the member is undefined if it's stored in the info
765 // but not in the state.
766
767 for (auto &it : obj_info->get_members()) {
768 const memberid_t &member_id = it.first;
769
770 if (state_members.find(member_id) == std::end(state_members)) {
771 // member is not in the state.
772 pending_members.insert(member_id);
773 }
774 }
775
776 for (auto &it : state_members) {
777 const memberid_t &member_id = it.first;
778 const Member &member = it.second;
779 nyan_op op = member.get_operation();
780
781 // member has = operation, so it's no longer pending.
782 if (op == nyan_op::ASSIGN) {
783 pending_members.erase(member_id);
784 }
785 }
786 }
787
788 if (pending_members.size() == 0) {
789 obj_values_ok.insert(obj_id);
790 }
791 else {
792 const Location &loc = it.second;
793
794 throw TypeError{
795 loc,
796 "this object has members without values: "s
797 + util::strjoin(", ", pending_members)
798 };
799 }
800 }
801
802 // TODO: check if @-overrides change an = to something else
803 // without a = remaining somewhere in a parent
804 // TODO: check if the @-depth is <= the patch depth
805
806 // TODO: check if adding parents would be cyclic
807 // TODO: check if adding parents leads to member name clashes
808 }
809
810
new_view()811 std::shared_ptr<View> Database::new_view() {
812 return std::make_shared<View>(shared_from_this());
813 }
814
815
816 } // namespace nyan
817