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 &current_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 &current_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