about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitignore12
-rw-r--r--LLVMprivateGenerator/Makefile12
-rw-r--r--LLVMprivateGenerator/main.cpp458
-rw-r--r--LLVMprivateGenerator/registered_structs.cpp267
-rw-r--r--wrapperhelper/CMakeLists.txt7
-rwxr-xr-xwrapperhelper/Makefile299
-rw-r--r--wrapperhelper/README.md179
-rw-r--r--wrapperhelper/ast.h189
-rw-r--r--wrapperhelper/example-libc.h170
-rw-r--r--wrapperhelper/gen.cpp1059
-rw-r--r--wrapperhelper/gen.h144
-rw-r--r--wrapperhelper/include-fixed/bits/stdint-intn.h32
-rw-r--r--wrapperhelper/include-fixed/bits/stdint-least.h39
-rw-r--r--wrapperhelper/include-fixed/bits/stdint-uintn.h32
-rw-r--r--wrapperhelper/include-fixed/bits/types.h192
-rw-r--r--wrapperhelper/include-fixed/stdint.h276
-rw-r--r--wrapperhelper/main.cpp69
-rw-r--r--wrapperhelper/src/cstring.c115
-rw-r--r--wrapperhelper/src/cstring.h78
-rw-r--r--wrapperhelper/src/generator.c922
-rw-r--r--wrapperhelper/src/generator.h50
-rw-r--r--wrapperhelper/src/khash.h699
-rw-r--r--wrapperhelper/src/lang.c1164
-rw-r--r--wrapperhelper/src/lang.h439
-rw-r--r--wrapperhelper/src/main.c184
-rw-r--r--wrapperhelper/src/parse.c3164
-rw-r--r--wrapperhelper/src/parse.h14
-rw-r--r--wrapperhelper/src/prepare.c372
-rw-r--r--wrapperhelper/src/prepare.h18
-rw-r--r--wrapperhelper/src/preproc.c2901
-rw-r--r--wrapperhelper/src/preproc.h17
-rw-r--r--wrapperhelper/src/vector.c105
-rw-r--r--wrapperhelper/src/vector.h213
-rw-r--r--wrapperhelper/utils.h33
34 files changed, 11603 insertions, 2321 deletions
diff --git a/.gitignore b/.gitignore
index 140a80eb..68cda85e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,11 +60,13 @@ build*/
 src/git_head.h
 backup/
 
-# LLVMprivateGenerator
-/LLVMprivateGenerator/*
-!/LLVMprivateGenerator/Makefile
-!/LLVMprivateGenerator/main.cpp
-!/LLVMprivateGenerator/registered_structs.cpp
+# Wrapper helper
+/wrapperhelper/bin
+/wrapperhelper/makedir
+/wrapperhelper/obj
+/wrapperhelper/sanaddress
+/wrapperhelper/sanleak
+/wrapperhelper/sanundefined
 
 # macOS
 .DS_Store
diff --git a/LLVMprivateGenerator/Makefile b/LLVMprivateGenerator/Makefile
deleted file mode 100644
index 61864262..00000000
--- a/LLVMprivateGenerator/Makefile
+++ /dev/null
@@ -1,12 +0,0 @@
-all: dumpSigs
-
-dumpSigs: main.o registered_structs.o
-	g++ -g3 -std=gnu++17 -fno-rtti main.o registered_structs.o -o dumpSigs "-L$(LLVM_install_dir)/lib" -lclang-cpp -lclangTooling -Wl,-rpath "-Wl,$(LLVM_install_dir)/lib"
-
-main.o: main.cpp
-	g++ -g3 -std=gnu++17 -fno-rtti -c main.cpp -Wfatal-errors "-I$(LLVM_install_dir)/include" -o main.o
-registered_structs.o: registered_structs.cpp
-	g++ -g3 -std=gnu++17 -fno-rtti -c registered_structs.cpp -Wfatal-errors -o registered_structs.o
-
-clean:
-	$(RM) dumpSigs main.o registered_structs.o
diff --git a/LLVMprivateGenerator/main.cpp b/LLVMprivateGenerator/main.cpp
deleted file mode 100644
index fe070cb0..00000000
--- a/LLVMprivateGenerator/main.cpp
+++ /dev/null
@@ -1,458 +0,0 @@
-#include <algorithm>
-#include <fstream>
-#include <iostream>
-#include <numeric>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include <llvm/Support/raw_ostream.h>
-#include <clang/AST/ASTConsumer.h>
-#include <clang/AST/Decl.h>
-#include <clang/AST/Mangle.h>
-#include <clang/AST/PrettyPrinter.h>
-#include <clang/AST/RecursiveASTVisitor.h>
-#include <clang/Frontend/CompilerInstance.h>
-#include <clang/Frontend/FrontendActions.h>
-#include <clang/Tooling/CommonOptionsParser.h>
-#include <clang/Tooling/Tooling.h>
-
-clang::MangleContext *mangler = nullptr;
-
-std::unordered_map<std::string, std::pair<std::string, bool>> funMap;
-std::vector<std::string> funList;
-
-bool isTypeTrivial(const clang::QualType &q, const clang::QualType *qorig);
-clang::QualType getPointedType(const clang::QualType q) {
-	if (const clang::PointerType *p = q->getAs<clang::PointerType>())
-		return getPointedType(p->getPointeeType());
-	else if (const clang::ReferenceType *r = q->getAs<clang::ReferenceType>())
-		return getPointedType(r->getPointeeType());
-	else
-		return q;
-}
-
-std::string record2name(const clang::RecordDecl &q, const clang::QualType *qorig) {
-	std::string s = q.getNameAsString();
-	if (s == "") {
-		if (!qorig) return "????.!";
-		if (const clang::TypedefType *tt = (*qorig)->getAs<clang::TypedefType>()) {
-			// Typedef
-			if (clang::TypedefNameDecl *td = tt->getDecl()) {
-				return td->getNameAsString();
-			} else {
-				return "<typedef with no declaration>";
-			}
-		} else {
-			return std::string("<unknown type ") + (*qorig)->getTypeClassName() + ">";
-		}
-	} else {
-		return s;
-	}
-}
-
-char ptr2char(const std::string &str) __attribute__((const));
-const char *ptr2str(const std::string &str) __attribute__((const));
-char type2char(const clang::QualType &qual /* Canonical */, const clang::QualType *qorig) {
-	if (qual->isBuiltinType()) {
-		switch (static_cast<const clang::BuiltinType&>(*qual).getKind()) {
-		case clang::BuiltinType::Kind::Void:
-			return 'v';
-		case clang::BuiltinType::Kind::Bool:
-			return 'i';
-		case clang::BuiltinType::Kind::Char_U:
-			return 'C';
-		case clang::BuiltinType::Kind::Char_S:
-			return 'c';
-		case clang::BuiltinType::Kind::Char8:
-			return 'c';
-		case clang::BuiltinType::Kind::UChar:
-			return 'C';
-		case clang::BuiltinType::Kind::SChar:
-			return 'c';
-		case clang::BuiltinType::Kind::WChar_U:
-			return 'W';
-		case clang::BuiltinType::Kind::UShort:
-			return 'W';
-		case clang::BuiltinType::Kind::WChar_S:
-			return 'w';
-		case clang::BuiltinType::Kind::Char16:
-			return 'w';
-		case clang::BuiltinType::Kind::Short:
-			return 'w';
-		case clang::BuiltinType::Kind::UInt:
-			return 'u';
-		case clang::BuiltinType::Kind::Char32:
-			return 'i';
-		case clang::BuiltinType::Kind::Int:
-			return 'i';
-		case clang::BuiltinType::Kind::ULong:
-			return 'L';
-		case clang::BuiltinType::Kind::Long:
-			return 'l';
-		case clang::BuiltinType::Kind::ULongLong:
-			return 'U';
-		case clang::BuiltinType::Kind::LongLong:
-			return 'I';
-		case clang::BuiltinType::Kind::UInt128:
-			return 'H';
-		case clang::BuiltinType::Kind::Int128:
-			return 'H';
-		case clang::BuiltinType::Kind::Float:
-			return 'f';
-		case clang::BuiltinType::Kind::Double:
-			return 'd';
-		case clang::BuiltinType::Kind::LongDouble:
-			return 'D';
-		case clang::BuiltinType::Kind::NullPtr:
-			return 'p'; // nullptr_t
-		
-		case clang::BuiltinType::Kind::Half:
-		case clang::BuiltinType::Kind::BFloat16:
-		case clang::BuiltinType::Kind::ShortAccum:
-		case clang::BuiltinType::Kind::Accum:
-		case clang::BuiltinType::Kind::LongAccum:
-		case clang::BuiltinType::Kind::UShortAccum:
-		case clang::BuiltinType::Kind::UAccum:
-		case clang::BuiltinType::Kind::ULongAccum:
-		case clang::BuiltinType::Kind::ShortFract:
-		case clang::BuiltinType::Kind::Fract:
-		case clang::BuiltinType::Kind::LongFract:
-		case clang::BuiltinType::Kind::UShortFract:
-		case clang::BuiltinType::Kind::UFract:
-		case clang::BuiltinType::Kind::ULongFract:
-		case clang::BuiltinType::Kind::SatShortAccum:
-		case clang::BuiltinType::Kind::SatAccum:
-		case clang::BuiltinType::Kind::SatLongAccum:
-		case clang::BuiltinType::Kind::SatUShortAccum:
-		case clang::BuiltinType::Kind::SatUAccum:
-		case clang::BuiltinType::Kind::SatULongAccum:
-		case clang::BuiltinType::Kind::SatShortFract:
-		case clang::BuiltinType::Kind::SatFract:
-		case clang::BuiltinType::Kind::SatLongFract:
-		case clang::BuiltinType::Kind::SatUShortFract:
-		case clang::BuiltinType::Kind::SatUFract:
-		case clang::BuiltinType::Kind::SatULongFract:
-		case clang::BuiltinType::Kind::Float16:
-		case clang::BuiltinType::Kind::Float128:
-		case clang::BuiltinType::Kind::Overload:
-		case clang::BuiltinType::Kind::BoundMember:
-		case clang::BuiltinType::Kind::PseudoObject:
-		case clang::BuiltinType::Kind::Dependent:
-		case clang::BuiltinType::Kind::UnknownAny:
-		case clang::BuiltinType::Kind::ARCUnbridgedCast:
-		case clang::BuiltinType::Kind::BuiltinFn:
-		case clang::BuiltinType::Kind::ObjCId:
-		case clang::BuiltinType::Kind::ObjCClass:
-		case clang::BuiltinType::Kind::ObjCSel:
-#define IMAGE_TYPE(it, id, si, a, s) case clang::BuiltinType::Kind::id:
-#include <clang/Basic/OpenCLImageTypes.def>
-#undef IMAGE_TYPE
-		case clang::BuiltinType::Kind::OCLSampler:
-		case clang::BuiltinType::Kind::OCLEvent:
-		case clang::BuiltinType::Kind::OCLClkEvent:
-		case clang::BuiltinType::Kind::OCLQueue:
-		case clang::BuiltinType::Kind::OCLReserveID:
-		case clang::BuiltinType::Kind::IncompleteMatrixIdx:
-		case clang::BuiltinType::Kind::OMPArraySection:
-		case clang::BuiltinType::Kind::OMPArrayShaping:
-		case clang::BuiltinType::Kind::OMPIterator:
-#define EXT_OPAQUE_TYPE(et, id, e) case clang::BuiltinType::Kind::id:
-#include <clang/Basic/OpenCLExtensionTypes.def>
-#define SVE_TYPE(n, id, si) case clang::BuiltinType::Kind::id:
-#include <clang/Basic/AArch64SVEACLETypes.def>
-#define PPC_VECTOR_TYPE(n, id, s) case clang::BuiltinType::Kind::id:
-#include <clang/Basic/PPCTypes.def>
-#undef EXT_OPAQUE_TYPE
-#undef SVE_TYPE
-#undef PPC_VECTOR_TYPE
-			return '!';
-		default:
-			return ':';
-		}
-	} else if (qual->isEnumeralType()) {
-		const clang::EnumDecl *ed = qual->getAs<clang::EnumType>()->getDecl();
-		if (!ed) {
-			return 'i';
-		} else {
-			return type2char(ed->getIntegerType().getCanonicalType(), qorig);
-		}
-	} else if (qual->isFunctionPointerType()) {
-		return '@';
-	} else if (qual->isAnyPointerType() || qual->isReferenceType()) {
-		const clang::QualType &pointed = getPointedType(qual);
-		if (isTypeTrivial(pointed, qorig)) {
-			return 'p';
-		} else if (const clang::RecordType *rct = pointed->getAs<clang::RecordType>()) {
-			clang::RecordDecl *rc = rct->getDecl();
-			if (!rc) {
-				return '!';
-			} else if (!rc->isCompleteDefinition()) {
-				return 'p';
-			} else {
-				std::string str;
-				if (qorig) {
-					const clang::QualType qpted = getPointedType(*qorig);
-					str = record2name(*rc, &qpted);
-				} else {
-					str = record2name(*rc, nullptr);
-				}
-				char ret = ptr2char(str);
-				if (ret) return ret;
-				else {
-					return '!';
-				}
-			}
-		} else {
-			return '!';
-		}
-	} else if (const clang::RecordType *rct = qual->getAs<clang::RecordType>()) {
-		clang::RecordDecl *rc = rct->getDecl();
-		if (!rc) {
-			return '?';
-		} else if (rc->getNameAsString() == "__builtin_va_list") {
-			// va_list
-			return 'A';
-		} else {
-			return '?';
-		}
-	} else {
-		return '?';
-	}
-}
-bool isTypeTrivial(const clang::QualType &q, const clang::QualType *qorig) {
-	const char c = type2char(q, qorig);
-#define GO(chr) || (c == chr)
-	return (c == 'v')
-		GO('i') GO('u')
-		GO('I') GO('U')
-		GO('l') GO('L')
-		GO('f') GO('d')
-		GO('D') GO('K')
-		GO('0') GO('1')
-		GO('C') GO('c')
-		GO('W') GO('w')
-		GO('H')
-		GO('p');
-#undef GO
-}
-bool isTypeValid(const clang::QualType &q, const clang::QualType *qorig) {
-	const char c = type2char(q, qorig);
-	if (c == 'A') return false;
-	if (c == 'V') return false;
-	return ((c >= '0') && (c <= '9')) || ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'));
-}
-
-const std::string type2string(const clang::QualType &qual, const clang::QualType *qorig) {
-	if (qual->isBuiltinType()) {
-		return std::string("(builtin) ") + static_cast<const clang::BuiltinType&>(*qual).getName(clang::PrintingPolicy{{}}).data();
-	} else if (qual->isFunctionPointerType()) {
-		return "Callback (function pointer)";
-	} else if (qual->isAnyPointerType() || qual->isReferenceType()) {
-		std::string prefix = qual->isAnyPointerType() ? "Pointer to " : "Reference to ";
-		const clang::QualType &pointed = getPointedType(qual);
-		if (isTypeTrivial(pointed, qorig)) {
-			return prefix + "trivial object " + type2string(pointed, qorig) + " (" + type2char(pointed, qorig) + ")";
-		} else if (const clang::RecordType *rct = pointed->getAs<clang::RecordType>()) {
-			clang::RecordDecl *rc = rct->getDecl();
-			if (!rc) {
-				return prefix + "unknown record";
-			} else if (!rc->isCompleteDefinition()) {
-				return prefix + "incomplete record " + rc->getNameAsString();
-			} else {
-				std::string str;
-				if (qorig) {
-					const clang::QualType qpted = getPointedType(*qorig);
-					str = record2name(*rc, &qpted);
-				} else {
-					str = record2name(*rc, nullptr);
-				}
-				const char *ret = ptr2str(str);
-				if (ret[0] != '\0') {
-					return prefix + ret;
-				} else {
-					if (mangler && mangler->shouldMangleDeclName(rc)) {
-						std::string mangled;
-						{
-							llvm::raw_string_ostream strstr{mangled};
-							mangler->mangleName(rc, strstr);
-						}
-						return prefix + "unknown record " + str + " (=== " + mangled + ")";
-					} else {
-						return prefix + "unknown record " + str;
-					}
-				}
-			}
-		} else {
-			return prefix + "non-trivial or typedef'ed object " + type2string(pointed, qorig) + " (" + type2char(pointed, qorig) + ")";
-			//return "Pointer (maybe to callback)";
-		}
-	} else if (qual->isEnumeralType()) {
-		const clang::EnumDecl *ed = qual->getAs<clang::EnumType>()->getDecl();
-		if (!ed) {
-			return "Enumeration with unknown underlying integer type (assuming int)";
-		} else {
-			return "Enumeration with underlying type " + type2string(ed->getIntegerType().getCanonicalType(), nullptr);
-		}
-	} else if (const clang::RecordType *rct = qual->getAs<clang::RecordType>()) {
-		clang::RecordDecl *rc = rct->getDecl();
-		if (!rc) {
-			return "Unknown record";
-		} else if (rc->getNameAsString() == "__builtin_va_list") {
-			return "va_list";
-		} else {
-			return "Unknown record " + std::string(rc->getName().data());
-		}
-	} else {
-		return std::string("??? ") + qual->getTypeClassName();
-	}
-}
-
-class Visitor : public clang::RecursiveASTVisitor<Visitor> {
-public:
-	clang::ASTContext &context;
-	
-	bool shouldVisitTemplateInstantiations() const /* override */ { return true; }
-	
-	Visitor(clang::CompilerInstance &ci) : context(ci.getASTContext()) {
-		if (!mangler) {
-			mangler = clang::ItaniumMangleContext::create(context, ci.getDiagnostics());
-		}
-	}
-	
-	~Visitor() {
-		if (mangler) {
-			delete mangler;
-			mangler = nullptr;
-		}
-	}
-	
-	bool VisitDecl(clang::Decl *decl) /* override */ {
-		std::cerr << std::flush;
-		if (!decl) return true;
-		
-		if ((decl->getKind() >= clang::Decl::Kind::firstFunction) && (decl->getKind() <= clang::Decl::Kind::lastFunction)) {
-			clang::DeclaratorDecl *ddecl = static_cast<clang::DeclaratorDecl*>(decl);
-			std::cout << "Function detected!\n";
-			
-			std::string funName{ddecl->getName()};
-			
-			auto niceprint = [](const std::string &infotype, const auto &dat){ std::cout << "    " << infotype << ": " << dat << "\n"; };
-			niceprint("Function name", funName);
-			if (mangler && mangler->shouldMangleDeclName(ddecl)) {
-				std::string mangled;
-				{
-					llvm::raw_string_ostream strstr{mangled};
-					mangler->mangleName(ddecl, strstr);
-				}
-				niceprint("Function mangled name", mangled);
-				funName = std::move(mangled);
-			}
-			
-			bool valid;
-			std::string funTypeStr{""};
-			if (ddecl->getFunctionType()->isFunctionNoProtoType()) {
-				const clang::FunctionNoProtoType *funType = static_cast<const clang::FunctionNoProtoType*>(ddecl->getFunctionType());
-				const auto &retType = funType->getReturnType();
-				
-				niceprint("Function return type", type2string(retType, &retType));
-				niceprint("Canonical function return type",
-					type2string(retType.getCanonicalType(), &retType) +
-					" (" + type2char(retType.getCanonicalType(), &retType) + ")");
-				niceprint("Is sugared", funType->isSugared());
-				if (funType->isSugared()) {
-					clang::QualType qft{funType, 0};
-					niceprint("Desugared", type2string(funType->desugar(), &qft));
-				}
-				
-				funTypeStr = type2char(retType.getCanonicalType(), &retType) + std::string("Fv");
-				valid = isTypeValid(retType.getCanonicalType(), &retType);
-			} else {
-				const clang::FunctionProtoType *funType = static_cast<const clang::FunctionProtoType*>(ddecl->getFunctionType());
-				const auto &retType = funType->getReturnType();
-				
-				niceprint("Function return type", type2string(retType, &retType));
-				niceprint("Canonical function return type",
-					type2string(retType.getCanonicalType(), &retType)
-					+ " (" + type2char(retType.getCanonicalType(), &retType) + ")");
-				niceprint("Parameter count", funType->getNumParams());
-				for (const clang::QualType &type : funType->getParamTypes()) {
-					niceprint("  " + type2string(type, &type),
-						type2string(type.getCanonicalType(), &type) + " (" + type2char(type.getCanonicalType(), &type) + ")");
-				}
-				niceprint("Variadic function", funType->isVariadic() ? "yes" : "no");
-				
-				funTypeStr =
-					type2char(retType.getCanonicalType(), &retType) +
-					((funType->getNumParams() == 0)
-					 ? std::string("Fv") : std::accumulate(funType->getParamTypes().begin(), funType->getParamTypes().end(), std::string("F"),
-											[](const std::string &acc, const clang::QualType &qual){ return acc + type2char(qual.getCanonicalType(), &qual); }));
-				if (funType->isVariadic()) funTypeStr += "V";
-				valid = !funType->isVariadic() &&
-					std::accumulate(funType->getParamTypes().begin(), funType->getParamTypes().end(), isTypeValid(retType.getCanonicalType(), &retType),
-						[](bool acc, const clang::QualType &qual){ return acc && isTypeValid(qual.getCanonicalType(), &qual); });
-			}
-			
-			niceprint("Conclusion", "");
-			niceprint("Function final name", funName);
-			niceprint("Function type", funTypeStr);
-			niceprint("Valid function type", valid ? "yes" : "no");
-			std::cout << "\n";
-			
-			funMap[funName] = std::make_pair(funTypeStr, valid);
-			funList.push_back(funName);
-		}
-		
-		return true;
-	}
-};
-
-class Consumer : public clang::ASTConsumer {
-public:
-	Visitor visitor;
-	
-	Consumer(clang::CompilerInstance &ci) : visitor(ci) {
-	}
-	
-	void HandleTranslationUnit(clang::ASTContext &context) override {
-		visitor.TraverseDecl(context.getTranslationUnitDecl());
-	}
-};
-
-class Action : public clang::ASTFrontendAction {
-public:
-	virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance &ci, llvm::StringRef inFile) override {
-		return std::make_unique<Consumer>(ci);
-	}
-};
-
-int main(int argc, const char **argv) {
-	if (argc < 2) {
-		std::cerr << "Usage: " << argv[0] << " (filenames) -- [-I...]" << std::endl;
-		return 2;
-	}
-	
-	/*int fakeargc = argc + 1;
-	const char **fakeargv = new const char*[fakeargc];
-	memcpy(fakeargv, argv, argc * sizeof(char*));
-	fakeargv[fakeargc - 1] = "--";*/
-	llvm::cl::OptionCategory opcat{""};
-	clang::tooling::CommonOptionsParser op{argc, argv, opcat};
-	std::vector<std::string> paths; for (int i = 1; i < argc; ++i) paths.push_back(argv[i]);
-	
-	clang::tooling::ClangTool tool{op.getCompilations(), paths};
-	
-	tool.run(clang::tooling::newFrontendActionFactory<Action>().get());
-	
-	std::cout << "Done, outputing output.h" << std::endl;
-	std::sort(funList.begin(), funList.end());
-	std::fstream file{"output.h", std::ios_base::out};
-	for (const std::string &funName : funList) {
-		if (!funMap[funName].second) {
-			file << "//";
-		}
-		file << "GO(" << funName << ", " << funMap[funName].first << ")\n";
-	}
-	
-	return 0;
-}
diff --git a/LLVMprivateGenerator/registered_structs.cpp b/LLVMprivateGenerator/registered_structs.cpp
deleted file mode 100644
index 0473deb5..00000000
--- a/LLVMprivateGenerator/registered_structs.cpp
+++ /dev/null
@@ -1,267 +0,0 @@
-#include <string>
-
-#define ALL START() \
-	/* libc */ \
-	STRUCT("_IO_FILE", "a FILE") \
-	STRUCT("_G_fpos_t", "a file position") \
-	STRUCT("sockaddr", "a socket address") \
-	STRUCT("itimerspec", "an itimerspec") \
-	STRUCT("timespec", "a timespec") \
-	STRUCT("itimerval", "an itimerval") \
-	STRUCT("timeval", "a timeval") \
-	STRUCT("timex", "a timex") \
-	STRUCT("timezone", "a timezone") \
-	STRUCT("dirent", "a dirent") \
-	STRUCT("dirent64", "a dirent64") \
-	STRUCT("__dirstream", "a dir stream") \
-	STRUCT("tm", "a time structure (tm)") \
-	STRUCT("cmsghdr", "a cmsghdr") \
-	STRUCT("msghdr", "a msghdr") \
-	STRUCT("rpcent", "an rpcent") \
-	STRUCT("random_data", "a random_data structure") \
-	STRUCT("drand48_data", "a drand48_data structure") \
-	STRUCT("termios", "a termios") \
-	STRUCT("iovec", "an iovec") \
-	STRUCT("file_handle", "a file handle") \
-	STRUCT("lconv", "an lconv") \
-	STRUCT("__locale_struct", "a locale structure") \
-	STRUCT("aliasent", "an alias") \
-	STRUCT("fstab", "an fstab") \
-	STRUCT("group", "a group") \
-	STRUCT("hostent", "a hostent") \
-	STRUCT("protoent", "a protoent") \
-	STRUCT("passwd", "a password") \
-	STRUCT("spwd", "an spwd") \
-	STRUCT("ttyent", "a ttyent") \
-	STRUCT("utmp", "an utmp structure") \
-	STRUCT("utmpx", "an utmpx structure") \
-	STRUCT("ifaddrs", "an ifaddrs structure") \
-	STRUCT("statfs", "a statfs structure") \
-	STRUCT("statfs64", "a statfs64 structure") \
-	STRUCT("statvfs", "a statvfs structure") \
-	STRUCT("statvfs64", "a statvfs64 structure") \
-	STRUCT("timeb", "a timeb structure") \
-	STRUCT("_ftsent", "an _ftsent structure") \
-	STRUCT("sysinfo", "a sysinfo structure") \
-	STRUCT("rlimit", "an rlimit structure") \
-	STRUCT("rlimit64", "an rlimit64 structure") \
-	STRUCT("rusage", "an rusage structure") \
-	STRUCT("entry", "an entry structure") \
-	STRUCT("pollfd", "a pollfd structure") \
-	STRUCT("re_pattern_buffer", "a re_pattern_buffer structure") \
-	STRUCT("sembuf", "a sembuf structure") \
-	STRUCT("tms", "a tms structure") \
-	STRUCT("utsname", "an utsname structure") \
-	STRUCT("utimbuf", "an utimbuf structure") \
-	STRUCT2("__va_list_tag", "__va_list_tag (aka, a va_list)", 'A') \
-	/* ncurses */ \
-	STRUCT("_win_st", "a _win_st structure") \
-	STRUCT("MEVENT", "an MEVENT structure") \
-	TYPEDEF("cchar_t", "a cchar_t") \
-	/* zlib */ \
-	STRUCT("gz_header_s", "a gz_header_s structure") \
-	STRUCT("gzFile_s", "a gzFile_s structure") \
-	STRUCT("z_stream_s", "a z_stream_s structure") \
-	\
-	END()
-
-#define START()
-#define STRUCT(s, ret) if (str == s) { return 'p'; } else
-#define STRUCT2(s, ret, c) if (str == s) { return c; } else
-#define TYPEDEF(s, ret) if (str == s) { return 'p'; } else
-#define END() { return 0; }
-char ptr2char(const std::string &str) {
-	/*if ((str == "_IO_FILE")
-	 || (str == "_G_fpos_t")
-	 || (str == "sockaddr")
-	 || (str == "itimerspec")
-	 || (str == "timespec")
-	 || (str == "itimerval")
-	 || (str == "timeval")
-	 || (str == "timex")
-	 || (str == "timezone")
-	 || (str == "dirent")
-	 || (str == "dirent64")
-	 || (str == "__dirstream")
-	 || (str == "tm")
-	 || (str == "cmsghdr")
-	 || (str == "msghdr")
-	 || (str == "rpcent")
-	 || (str == "random_data")
-	 || (str == "drand48_data")
-	 || (str == "termios")
-	 || (str == "iovec")
-	 || (str == "file_handle")
-	 || (str == "lconv")
-	 || (str == "__locale_struct")
-	 || (str == "aliasent")
-	 || (str == "fstab")
-	 || (str == "group")
-	 || (str == "hostent")
-	 || (str == "protoent")
-	 || (str == "passwd")
-	 || (str == "spwd")
-	 || (str == "ttyent")
-	 || (str == "utmp")
-	 || (str == "utmpx")
-	 || (str == "ifaddrs")
-	 || (str == "statfs")
-	 || (str == "statfs64")
-	 || (str == "statvfs")
-	 || (str == "timeb")
-	 || (str == "_ftsent")
-	 || (str == "sysinfo")
-	 || (str == "rlimit")
-	 || (str == "rlimit64")
-	 || (str == "rusage")
-	 || (str == "entry")
-	 || (str == "pollfd")
-	 || (str == "re_pattern_buffer")
-	 || (str == "sembuf")
-	 || (str == "tms")
-	 || (str == "utsname")
-	 || (str == "utimbuf")
-	// ncurses
-	 || (str == "_win_st")
-	 
-	 || (str == "cchar_t")
-	) {
-		// FILE*, fpos_t*, ...
-		return 'p';
-	} else if (str == "__va_list_tag") {
-		return 'A';
-	} else {
-		return 0;
-	}*/
-	ALL
-}
-#undef END
-#undef TYPEDEF
-#undef STRUCT2
-#undef STRUCT
-#undef START
-
-#define START()
-#define STRUCT(s, ret) if (str == s) { return ret; } else
-#define STRUCT2(s, ret, c) if (str == s) { return ret; } else
-#define TYPEDEF(s, ret) if (str == s) { return ret; } else
-#define END() return "";
-const char *ptr2str(const std::string &str) {
-	/*if (str == "_IO_FILE") {
-		return "a FILE";
-	} else if (str == "_G_fpos_t") {
-		return "a file position";
-	} else if (str == "sockaddr") {
-		return "a socket address";
-	} else if (str == "itimerspec") {
-		return "an itimerspec";
-	} else if (str == "timespec") {
-		return "a timespec";
-	} else if (str == "itimerval") {
-		return "an itimerval";
-	} else if (str == "timeval") {
-		return "a timeval";
-	} else if (str == "timex") {
-		return "a timex";
-	} else if (str == "timezone") {
-		return "a timezone";
-	} else if (str == "dirent") {
-		return "a dirent";
-	} else if (str == "dirent64") {
-		return "a dirent64";
-	} else if (str == "__dirstream") {
-		return "a dir stream";
-	} else if (str == "tm") {
-		return "a time structure (tm)";
-	} else if (str == "cmsghdr") {
-		return "a cmsghdr";
-	} else if (str == "msghdr") {
-		return "a msghdr";
-	} else if (str == "rpcent") {
-		return "an rpcent";
-	} else if (str == "random_data") {
-		return "a random_data structure";
-	} else if (str == "drand48_data") {
-		return "a drand48_data structure";
-	} else if (str == "termios") {
-		return "a termios";
-	} else if (str == "iovec") {
-		return "an iovec";
-	} else if (str == "file_handle") {
-		return "a file handle";
-	} else if (str == "lconv") {
-		return "an lconv";
-	} else if (str == "__locale_struct") {
-		return "a locale structure";
-	} else if (str == "aliasent") {
-		return "an alias";
-	} else if (str == "fstab") {
-		return "an fstab";
-	} else if (str == "group") {
-		return "a group";
-	} else if (str == "hostent") {
-		return "a hostent";
-	} else if (str == "protoent") {
-		return "a protoent";
-	} else if (str == "passwd") {
-		return "a password";
-	} else if (str == "spwd") {
-		return "an spwd";
-	} else if (str == "ttyent") {
-		return "a ttyent";
-	} else if (str == "utmp") {
-		return "an utmp structure";
-	} else if (str == "utmpx") {
-		return "an utmpx structure";
-	} else if (str == "ifaddrs") {
-		return "an ifaddrs structure";
-	} else if (str == "statfs") {
-		return "a statfs structure";
-	} else if (str == "statfs64") {
-		return "a statfs64 structure";
-	} else if (str == "statvfs") {
-		return "a statvfs structure";
-	} else if (str == "statvfs64") {
-		return "a statvfs64 structure";
-	} else if (str == "timeb") {
-		return "a timeb structure";
-	} else if (str == "_ftsent") {
-		return "an _ftsent structure";
-	} else if (str == "sysinfo") {
-		return "a sysinfo structure";
-	} else if (str == "rlimit") {
-		return "an rlimit structure";
-	} else if (str == "rlimit64") {
-		return "an rlimit64 structure";
-	} else if (str == "rusage") {
-		return "an rusage structure";
-	} else if (str == "entry") {
-		return "an entry structure";
-	} else if (str == "pollfd") {
-		return "a pollfd structure";
-	} else if (str == "re_pattern_buffer") {
-		return "a re_pattern_buffer structure";
-	} else if (str == "sembuf") {
-		return "a sembuf structure";
-	} else if (str == "tms") {
-		return "a tms structure";
-	} else if (str == "utsname") {
-		return "an utsname structure";
-	} else if (str == "utimbuf") {
-		return "an utimbuf structure";
-	} else if (str == "__va_list_tag") {
-		return "__va_list_tag (aka, a va_list)";
-	// ncurses
-	} else if (str == "_win_st") {
-		return "a _win_st structure";
-		
-	} else if (str == "cchar_t") {
-		return "a cchar_t";
-	} else return "";*/
-	ALL
-}
-#undef END
-#undef TYPEDEF
-#undef STRUCT2
-#undef STRUCT
-#undef START
diff --git a/wrapperhelper/CMakeLists.txt b/wrapperhelper/CMakeLists.txt
deleted file mode 100644
index 3ab9cf14..00000000
--- a/wrapperhelper/CMakeLists.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-cmake_minimum_required(VERSION 3.10)
-project("helper")
-find_package(Clang REQUIRED CONFIG)
-
-add_executable(helper main.cpp gen.cpp)
-target_include_directories(helper SYSTEM PUBLIC ${CLANG_INCLUDE_DIRS})
-target_link_libraries(helper PUBLIC clang-cpp LLVM)
diff --git a/wrapperhelper/Makefile b/wrapperhelper/Makefile
new file mode 100755
index 00000000..9f1a6c5f
--- /dev/null
+++ b/wrapperhelper/Makefile
@@ -0,0 +1,299 @@
+all:
+.PHONY: all
+
+OPTIM?=2
+DEBUG?=1
+# FORCE_COLOR: set to non-empty, non 0 to force colorized output
+# ECHO: set to non-empty, non 0 to echo commands out
+
+help:
+	@echo 'Targets:'
+	@echo '  help (current target)'
+	@echo '  all (default target)'
+	@echo '  [EXE] - wrapperhelper'
+	@printf ' $(foreach obj,$(OBJLIST_wrapperhelper), [OBJ] - $(obj)\n)'
+	@printf ' $(foreach test,$(TESTS), [TST] - $(test)\n)'
+	@echo '  clean'
+	@echo '  distclean'
+	@echo ''
+	@echo 'Options:'
+	@echo '  OPTIM: GCC optimization level (-O is prepended) [default: 2]'
+	@echo '  DEBUG: set to 0 for release build, set to non-0 for debug build [default: 1]'
+	@echo '  FORCE_COLOR: set to non-0 to force colorized output'
+	@echo '  ECHO: set to non-0 to echo out commands executed'
+	@echo ''
+	@echo 'Current flags:'
+	@echo '  CPPFLAGS = $(CPPFLAGS)'
+	@echo '  CFLAGS   = $(CFLAGS)'
+#	@echo '  CXXFLAGS = $(CXXFLAGS)' unused
+	@echo '  LDFLAGS  = $(LDFLAGS)'
+	@echo '  LDLIBS  = $(LDLIBS)'
+	@echo ''
+	@echo 'Sanitizers:'
+	@echo "  address ------------ `[ $(ASAN_ON) -eq 1 ] && printf '\033[92mON\033[m' || printf '\033[91mOFF\033[m'`"
+	@echo "  leak --------------- `[ $(LSAN_ON) -eq 1 ] && printf '\033[92mON\033[m' || printf '\033[91mOFF\033[m'`"
+	@echo "  undefined behavior - `[ $(USAN_ON) -eq 1 ] && printf '\033[92mON\033[m' || printf '\033[91mOFF\033[m'`"
+.PHONY: help
+
+ifeq ($(ECHO:0=),)
+SILENCER:=@
+else
+SILENCER:=
+endif
+
+ifneq ($(strip $(DEBUG)),0)
+CPPFLAGS+= -DDEBUG -D_NRELEASE
+CFLAGS+= -g
+CXXFLAGS+= -g
+LDFLAGS+= -g
+OBJDIR?=debug
+else
+CPPFLAGS+= -DRELEASE -D_NDEBUG
+OBJDIR?=release
+endif
+
+COMMON_WARNINGS:=-Wfatal-errors -fanalyzer -Wall -Wextra
+COMMON_WARNINGS+= -Walloc-zero -Wcast-align=strict -Wcast-qual -Wconversion -Wdate-time
+COMMON_WARNINGS+= -Wdisabled-optimization -Wduplicated-branches -Wfloat-equal -Wformat-truncation=2
+COMMON_WARNINGS+= -Wimplicit-fallthrough=3 -Wlogical-op -Wmissing-format-attribute -Wmissing-include-dirs
+COMMON_WARNINGS+= -Wmissing-noreturn -Wnull-dereference -Wredundant-decls -Wundef -Wunreachable-code -Wshift-overflow=2
+COMMON_WARNINGS+= -Wstringop-overflow=4
+#COMMON_WARNINGS+= -Wstringop-overflow=4 -Wsuggest-attribute=cold -Wsuggest-attribute=const -Wsuggest-attribute=format
+#COMMON_WARNINGS+= -Wsuggest-attribute=malloc -Wsuggest-attribute=noreturn -Wsuggest-attribute=pure
+COMMON_WARNINGS+= -Wunknown-pragmas -Wunused-macros -Wwrite-strings
+COMMON_WARNINGS+= -Werror=attribute-alias=2 -Werror=duplicated-cond -Werror=format=2 -Werror=format-overflow=2
+COMMON_WARNINGS+= -Werror=format-signedness -Werror=pointer-arith
+COMMON_WARNINGS+= -Werror=return-type -Werror=shadow -Werror=strict-overflow -Werror=switch-enum
+CFLAGS_WARNINGS:=-Werror=implicit-function-declaration -Werror=jump-misses-init -Werror=strict-prototypes
+CXXFLAGS_WARNINGS:=-Werror=overloaded-virtual -fdiagnostics-show-template-tree -Wno-analyzer-use-of-uninitialized-value
+
+CPPFLAGS+=
+CFLAGS:=$(COMMON_WARNINGS) $(CFLAGS_WARNINGS) $(CFLAGS) -std=gnu18 -O$(OPTIM)
+CXXFLAGS:=$(COMMON_WARNINGS) $(CXXFLAGS_WARNINGS) $(CXXFLAGS) -std=c++20 -O$(OPTIM)
+LDFLAGS+= -O$(OPTIM)
+
+#CPPFLAGS+= -I/usr/include/SDL2 -D_REENTRANT -pthread
+#CFLAGS+= -pthread
+#CXXFLAGS+= -pthread
+#LDLIBS+= -pthread -lSDL2
+
+ifeq (,$(wildcard $(CURDIR)/sanaddress))
+ASAN_ON:=0
+else
+ASAN_ON:=1
+CFLAGS+= -fsanitize=address
+CXXFLAGS+= -fsanitize=address
+LDFLAGS+= -fsanitize=address
+endif
+ifeq (,$(wildcard $(CURDIR)/sanleak))
+LSAN_ON:=0
+else
+LSAN_ON:=1
+CFLAGS+= -fsanitize=leak
+CXXFLAGS+= -fsanitize=leak
+LDFLAGS+= -fsanitize=leak
+endif
+ifeq (,$(wildcard $(CURDIR)/sanundefined))
+USAN_ON:=0
+else
+USAN_ON:=1
+CFLAGS+= -fsanitize=undefined
+CXXFLAGS+= -fsanitize=undefined
+LDFLAGS+= -fsanitize=undefined
+endif
+
+# Default
+# .SUFFIXES: .out .a .ln .o .c .cc .C .cpp .p .f .F .m .r .y .l .ym .yl .s .S .mod
+# .sym .def .h .info .dvi .tex .texinfo .texi .txinfo .w .ch .web .sh .elc .el
+SUFFIXES =
+.SUFFIXES:
+.SECONDEXPANSION:
+
+ifneq ($(MAKECMDGOALS:distclean=clean),clean)
+.: ;
+bin obj: ; $(SILENCER)test -d $@ || mkdir $@
+# $(eval $(call reproduce_tree,<base>))
+define reproduce_tree =
+$(1) $(1)/parser: | $$$$(@D) ; $(SILENCER)test -d $$@ || mkdir $$@
+endef
+$(eval $(call reproduce_tree,obj/$(OBJDIR)))
+$(eval $(call reproduce_tree,obj/$(OBJDIR)/tests))
+$(eval $(call reproduce_tree,makedir))
+$(eval $(call reproduce_tree,makedir/tests))
+$(eval $(call reproduce_tree,tests))
+endif
+
+# Colors:
+# -------
+#   +--------+-----+
+#   |   3    |  9  |
+# +-+--------+-----+
+# |0|        |     | Black
+# |1|        | RM  | Red
+# |2|        |[MSG]| Green
+# |3|Creating|     | Yellow
+# |4|        | CP  | Blue
+# |5|        | LD  | Purple
+# |6|  C++   |     | Cyan
+# |7|        |     | Gray/white
+# +-+--------+-----+
+
+# $(call colorize,<br_color>,<br_text>,<text_color>,<text>)
+ifdef $(if $(FORCE_COLOR:0=),FORCE_COLOR,MAKE_TERMOUT)
+CFLAGS:=$(CFLAGS) -fdiagnostics-color
+CXXFLAGS:=$(CFLAGS) -fdiagnostics-color
+colorize=@printf "\033[$(1)m[$(2)]\033[m \033[$(3)m$(4)\033[m\n"
+else
+ifeq ($(SILENCER),)
+colorize=
+else
+colorize=@echo "[$(2)] $(4)"
+endif
+endif
+
+define newline :=
+
+
+endef
+
+# $(call remove,<list of file names to remove>)
+define remove =
+$(call colorize,1;91,RM ,91,Removing $(1))
+$(SILENCER)$(RM) -r $(1)
+endef
+
+# $(eval $(call add_deptree,<compiler with flags>,<output_filename_noext>,<input_filename_withoutsrc>))
+ifeq ($(MAKECMDGOALS),distclean)
+add_deptree=
+else
+define add_deptree =
+makedir/$(2).mk: | $$$$(@D)
+	$(call colorize,95,DEP,33,Creating $(3) dependancies)
+	$(SILENCER)set -e; $(1) -MM src/$(3) \
+	 | sed 's,\($$(notdir $$(basename $(3)))\)\.o[ :]*,$$(dir obj/$(OBJDIR)/$(3))\1.o: $$@'"\n"'$$(dir obj/$(OBJDIR)/$(3))\1.o $$@: ,g' >$$@
+include makedir/$(2).mk
+endef
+endif
+
+OBJLIST=$(OBJLIST_wrapperhelper) $(foreach test,$(TESTS),$(call test_o,$(test)))
+OBJLIST_wrapperhelper:=
+TESTS:=
+
+# $(call wrapperhelper_o,<base_dir>,<source_filename>,<output_filename>)
+wrapperhelper_o=obj/$(OBJDIR)/$(1)$(3).o
+# $(eval $(call compile_wrapperhelper_c,<base_dir>,<source_filename>,<output_filename>))
+define compile_wrapperhelper_c =
+$$(eval $$(call add_deptree,$$(CC) $$(CPPFLAGS) $$(CFLAGS),$(1)$(3),$(1)$(2).c))
+OBJLIST_wrapperhelper+= $(call wrapperhelper_o,$(1),$(2),$(3))
+$(call wrapperhelper_o,$(1),$(2),$(3)): src/$(1)$(2).c | $$$$(@D)
+	$(call colorize,36, C ,92,Compiling $$@)
+	$(SILENCER)$$(CC) $$(CPPFLAGS) $$(CFLAGS) -c src/$(1)$(2).c -o $$@
+endef
+# $(eval $(call compile_wrapperhelper_cxx,<base_dir>,<source_filename>,<output_filename>))
+define compile_wrapperhelper_cxx =
+$$(eval $$(call add_deptree,$$(CXX) $$(CPPFLAGS) $$(CXXFLAGS),$(1)$(3),$(1)$(2).cpp))
+OBJLIST_wrapperhelper+= $(call wrapperhelper_o,$(1),$(2),$(3))
+$(call wrapperhelper_o,$(1),$(2),$(3)): src/$(1)$(2).cpp | $$$$(@D)
+	$(call colorize,36,C++,92,Compiling $$@)
+	$(SILENCER)$$(CXX) $$(CPPFLAGS) $$(CXXFLAGS) -c src/$(1)$(2).cpp -o $$@
+endef
+
+# $(eval $(call compile_test_c,<test_dir/name>))
+define compile_test_c =
+$$(eval $$(call add_deptree,$$(CC) $$(CPPFLAGS) -Isrc/tests -Isrc $$(CFLAGS),tests/$(1),tests/$(1).c))
+TESTS+= $(1)
+tests/$(1): obj/$(OBJDIR)/tests/$(1).o | $$$$(@D)
+	$(call colorize,95,LD ,92,Linking $$@)
+	$(SILENCER)$$(CC) $$(LDFLAGS) -o $$@ obj/$(OBJDIR)/tests/$(1).o $$(LDLIBS)
+
+obj/$(OBJDIR)/tests/$(1).o: src/tests/$(1).c | $$$$(@D)
+	$(call colorize,36,C++,92,Compiling $$@)
+	$(SILENCER)$$(CC) $$(CPPFLAGS) -Isrc/tests -Isrc $$(CFLAGS) -c src/tests/$(1).c -o $$@
+endef
+# $(eval $(call compile_test_cxx,<test_dir/name>))
+define compile_test_cxx =
+$$(eval $$(call add_deptree,$$(CXX) $$(CPPFLAGS) -Isrc/tests -Isrc $$(CXXFLAGS),tests/$(1),tests/$(1).cpp))
+TESTS+= $(1)
+tests/$(1): obj/$(OBJDIR)/tests/$(1).o | $$$$(@D)
+	$(call colorize,95,LD ,92,Linking $$@)
+	$(SILENCER)$$(CXX) $$(LDFLAGS) -o $$@ obj/$(OBJDIR)/tests/$(1).o $$(LDLIBS)
+
+obj/$(OBJDIR)/tests/$(1).o: src/tests/$(1).cpp | $$$$(@D)
+	$(call colorize,36,C++,92,Compiling $$@)
+	$(SILENCER)$$(CXX) $$(CPPFLAGS) -Isrc/tests -Isrc $$(CXXFLAGS) -c src/tests/$(1).cpp -o $$@
+endef
+
+$(eval $(call compile_wrapperhelper_c,,cstring,cstring))
+$(eval $(call compile_wrapperhelper_c,,generator,generator))
+$(eval $(call compile_wrapperhelper_c,,lang,lang))
+$(eval $(call compile_wrapperhelper_c,,main,main))
+$(eval $(call compile_wrapperhelper_c,,parse,parse))
+$(eval $(call compile_wrapperhelper_c,,prepare,prepare))
+$(eval $(call compile_wrapperhelper_c,,preproc,preproc))
+$(eval $(call compile_wrapperhelper_c,,vector,vector))
+$(call wrapperhelper_o,,preproc,preproc): CFLAGS+= -fno-analyzer
+$(call wrapperhelper_o,,parse,parse): CFLAGS+= -fno-analyzer
+
+#$(eval $(call compile_test_cxx,core/number))
+
+bin/wrapperhelper: $$(OBJLIST_wrapperhelper) | $$(@D)
+	$(call colorize,95,LD ,92,Linking $@)
+	$(SILENCER)$(CXX) $(LDFLAGS) -o $@ $(OBJLIST_wrapperhelper) $(LDLIBS)
+
+wrapperhelper: bin/wrapperhelper
+alltests: $(TESTS:%=tests/%)
+.PHONY: wrapperhelper alltests
+
+all: wrapperhelper alltests
+
+clean:
+	$(call remove,$(OBJLIST))
+	$(call remove,bin/wrapperhelper)
+	$(call remove,$(TESTS:%=obj/$(OBJDIR)/tests/%.o))
+	$(call remove,$(TESTS:%=tests/%))
+.PHONY: clean
+distclean:
+	$(call remove,makedir)
+	$(call remove,obj)
+	$(call remove,bin tests)
+.PHONY: distclean
+
+sanitize/help:
+	@echo "Sanitizers:"
+	@echo "- address (removes leak)"
+	@echo "- leak (removes address)"
+	@echo "- undefined behavior"
+	@echo ""
+	@echo "Currently active options:"
+	@[ $(ASAN_ON) -eq 0 ] || echo "- address"
+	@[ $(LSAN_ON) -eq 0 ] || echo "- leak"
+	@[ $(USAN_ON) -eq 0 ] || echo "- undefined behavior"
+sanitize/address:
+	@[ $(ASAN_ON) -eq 0 ] && echo "Not sanitizing address" || echo "Sanitizing address"
+sanitize/leak:
+	@[ $(LSAN_ON) -eq 0 ] && echo "Not sanitizing leak" || echo "Sanitizing leak"
+sanitize/undefined:
+	@[ $(USAN_ON) -eq 0 ] && echo "Not sanitizing undefined behavior" || echo "Sanitizing undefined behavior"
+sanitize/address/on:
+	$(SILENCER)touch sanaddress
+	$(SILENCER)$(RM) sanleak
+sanitize/leak/on:
+	$(SILENCER)touch sanleak
+	$(SILENCER)$(RM) sanaddress
+sanitize/undefined/on:
+	$(SILENCER)touch sanundefined
+sanitize/address/off:
+	$(SILENCER)$(RM) sanaddress
+sanitize/leak/off:
+	$(SILENCER)$(RM) sanleak
+sanitize/undefined/off:
+	$(SILENCER)$(RM) sanundefined
+.PHONY: sanitize/address     sanitize/leak     sanitize/undefined
+.PHONY: sanitize/address/on  sanitize/leak/on  sanitize/undefined/on
+.PHONY: sanitize/address/off sanitize/leak/off sanitize/undefined/off
+
+tree:
+	@tree src
+.PHONY: tree
+
+.DELETE_ON_ERROR:
diff --git a/wrapperhelper/README.md b/wrapperhelper/README.md
index 1edd4db1..efbcfb51 100644
--- a/wrapperhelper/README.md
+++ b/wrapperhelper/README.md
@@ -1,99 +1,122 @@
 # Wrapper helper
 
-**WARNING: There are still many problems with this tool. Please do NOT submit code generated directly by the tool, you should only use it as a preliminary reference.**
+This folder is semi-independent from the parent project (`box64`). This sub-project aims to (partially) automating the generation of the private headers in `src/wrapped`. This is, however, still a work-in-progress and in alpha.
 
+As such, **this sub-project is mainly aimed at people who know how to read code and are familiar with the wrapped libraries part of `box64`**.
 
-This tool is based on libclangtooling.
+## Licensing
 
-It parses the AST of the library header files, generating the required structures of the wrapping library, including:
-- structure definitions,
-- export function signatures,
-- callback function wrapping,
-etc. Of course, this cannot completely automate everything, it can only be used as a reference.
+This program is under the MIT license. However, some system header files under the LGPL license (copied from a GNU libc Arch Linux installation) have been adapted into the `include-fixed` folder; these files are not copied into the output and simply serve as data. As such, I believe this falls under fair use, and does not lead to the output of this program (used in the parent `box64` project) being under the (L)GPL license.
 
-At the same time, this tool is also quite rough, and may even have errors.
+## Compiling
 
-## Build
+You need a C compiler and GNU Make. No library is required.
 
-```
-sudo apt install libclang-14-dev
-cd wrapperhelper
-mkdir build; cd build; cmake ..
-make
-```
+Go to this folder, then run the `make` command. This will produce a binary called `bin/wrapperhelper`.
 
-## Usage:
+This project has been compiled and tested with `GCC 14.2.1 20240805` on an `x86_64` machine, with no warning emitted.
 
-    helper <filename> <libname> [guest_triple] [host_triple] -- <clang_flags>
-            <filename>    : set the header file to be parsed
-            <libname>     : set libname required for wrapping func
-            [guest_triple]: set guest triple: can be arm32/arm64/x86/x64, default is x64
-            [host_triple] : set host triple: can be arm32/arm64/x86/x64, default is arm64
-            --            : mandatory
-            <clang_flags> : extra compiler flags
+You may also use the `make clean` and `make distclean` commands to remove output files (`clean`) and directories (`distclean`).
 
-### Usage example:
+## Usage
 
-`./helper /usr/include/jpeglib.h libjpeg x64 arm64 -- -I /usr/lib/gcc/x86_*/12.2.0/include --include /usr/lib/gcc/x86_*/12.2.0/include/stddef.h --include /usr/include/stdio.h`
+To use the wrapper helper, run the following command in the folder containing this `README.md`:
+```sh
+bin/wrapperhelper "path_to_support_file" "path_to_private.h" "path_to_private.h"
+```
 
-You would see an output similar to the files `src/wrapped/wrappedlibjpeg.c` and `src/wrapped/wrappedlibjpeg_private.h`, should they exist.
+The first file is a `C` file containing every declaration required. The second file is the "requests" input. The third file is the output file, which may be a different file.
 
-If there are multiple header files to process, write them into a custom header file as input.
+The support file may contain pragma declarations of the form
+```c
+#pragma wrappers explicit_simple TYPE
+```
+where `TYPE` is a `typedef` to a structure. This marks the structure pointed to by `TYPE` as "simple", which means that functions taking such structures are not required to be `GOM`-like.
+
+System headers included (directly or indirectly) by the support file are overriden by the files in `include-fixed`.
 
-### Output sample
+The first three lines of the input are ignored.
 
-Using the command above, we get the following (trimmed) files:
+A "request" is a structure containing an object name and, eventually, a default value (`GO`, `GO2` with type `vFiV` to function `xxx`, `DATA`...) and/or a "solved" value (which is similar, but deduced from the support file).
 
-In `wrappedlibjpeg_private.h`:
+Valid requests (in the reference file) are:
 ```c
-...
-GO(jpeg_quality_scaling, iFi)
-...
-GOM(jpeg_destroy, vFEp)
-...
+{GO/GOM/GOW/GOWM} ( name , type )
+{GOD/GO2/GOWD/GOW2} ( name , type , name )
+// {GO/GOM/GOW/GOWM} ( name ,
+// {GO/GOM/GOW/GOWM} ( name , type )
+// {GOD/GO2/GOWD/GOW2} ( name ,
+// {GOD/GO2/GOWD/GOW2} ( name , type , name )
+DATA[V/B/M] ( name , int )
+// DATA[V/B/M] ( name ,
+// DATA[V/B/M] ( name , int )
+```
+(where `{A/B}` means `A` or `B` and `[A/B]` means `A`, `B` or nothing). All other comments are ignored.
+
+If you want to explore the output of the different stages of the helper, you can use the following forms:
+```sh
+bin/wrapperhelper --prepare "path_to_support_file" # (1)
+bin/wrapperhelper --preproc "path_to_support_file" # (2)
+bin/wrapperhelper --proc "path_to_support_file"    # (3)
+bin/wrapperhelper "path_to_support_file"           # (3) as well
 ```
+1. This form outputs the list of preprocessor tokens (the "post-prepare" phase).
+2. This form outputs the list of processor tokens (the "post-preprocessor" phase).
+3. This form outputs the list of constants, type definitions, structure definitions, and declarations (the "post-processor" phase).
 
-In `wrappedlibjpeg.c`:
-```c
-...
-typedef struct jpeg_source_mgr {
-    void *next_input_byte;
-    unsigned long bytes_in_buffer;
-    vFp_t init_source;
-    iFp_t fill_input_buffer;
-    vFpI_t skip_input_data;
-    iFpi_t resync_to_restart;
-    vFp_t term_source;
-} jpeg_source_mgr, *jpeg_source_mgr_ptr;
-...
-#define GO(A) \
-static uintptr_t my_term_source_fct_##A = 0; \
-void  my_term_source_##A(struct jpeg_decompress_struct * a0) { \
-    return RunFunction(my_context, my_term_source_fct_##A, 1, a0); \
-}
-SUPER()
-#undef GO
-static void* findterm_sourceFct(void* fct) {
-    if(!fct) return fct;
-    if(GetNativeFnc((uintptr_t)fct)) return GetNativeFnc((uintptr_t)fct);
-    #define GO(A) if(my_term_source_fct_##A == (uintptr_t)fct) return my_term_source_##A;}
-    SUPER()
-    #undef GO
-    #define GO(A) if(my_term_source_fct_##A == 0) {my_term_source_fct_##A = (uintptr_t)fct;return my_term_source_##A;}
-    SUPER()
-    #undef GO
-    return NULL;
-}
-...
-EXPORT int my_jpeg_quality_scaling(void *emu, int  quality) {
-    libjpeg_my_t *my = (libjpeg_my_t*)my_lib->priv.w.p2;
-    my->jpeg_quality_scaling(quality);
-}
-...
-EXPORT void my_jpeg_destroy(void *emu, struct jpeg_common_struct * cinfo) {
-    // WARN: This function's arg has a structure ptr which is special, may need to wrap it for the host
-    libjpeg_my_t *my = (libjpeg_my_t*)my_lib->priv.w.p2;
-    my->jpeg_destroy(cinfo);
-}
-...
+### Example
+
+To remake the `wrappedlibc_private.h` file, use the following command:
+```sh
+bin/wrapperhelper example-libc.h ../src/wrapped/wrappedlibc_private.h ../src/wrapped/wrappedlibc_private.h
 ```
+This will emit a few marnings and (non-fatal) errors, then write the result directly in `wrappedlibc_private.h`.
+
+## Maintaining
+
+All of the source code is included in the `src` folder.
+
+The `main` function is in `main.c`.
+
+The first phase of compilation (steps 1-3 and a part of step 5 of the translation phases) is implemented in `prepare.c`.
+
+The second phase of compilation (steps 4 and 6) is implemented in `preproc.c`.
+
+The third phase of compilation (step 7) is implemented in `parse.c`, though no actual parsing of function definitions takes place.
+
+The reading and writing of the `_private.h` files is implemented in `generator.c`.
+
+## Known issues
+
+This project only works for `box64`; more work is required for this to be compatible with `box32`.
+
+Only native structures are read. This means that the current version of `wrapperhelper` does not detect an issue when a structure has different members or alignments in two different architectures.
+
+The include paths are hard-coded. There should instead be a structure passed around containing all arch-dependent informations.
+
+Similarly, structure letters (i.e. `S` for `struct _IO_FILE*`) are hard-coded. A pragma should be used instead (`#pragma wrappers type_letter IDENT type-name`, parsed as `PTOK_PRAGMA: Type is letter: <char>`, followed by `type-name`, followed by `PTOK_NEWLINE`).
+
+Conditionals in the `_private.h` files are ignored, except for taking only the negative branch. Manual cleanup of the output is required.
+
+Line numbers are missing entirely. For most errors, finding the corresponding file is difficult (though possible).
+
+Phase 5 is partially implemented, but could be greatly improved.
+
+The following features are missing from the generator:
+- Large structures as a parameter
+- Large structure as a return type (more than 16 bytes)
+- Atomic types
+
+The following features are missing from the preprocessor:
+- Error display (`#error` will stop the compilation, but a generic error message will be written)
+- General token concatenation (though the concatenation of two `PTOK_IDENT` works without issue)
+- Stringify
+- Skipped unexpected token warnings
+- Proper out-of-memory error handling
+
+The following features are missing from the parser:
+- `inline` and `_Noreturn`
+- `_Atomic(type-name)`
+- `_Alignas(type-name)` and `_Alignas(constant-expression)`
+- `(type-name){initializer-list}`
+- Function definitions are ignored, not parsed
diff --git a/wrapperhelper/ast.h b/wrapperhelper/ast.h
deleted file mode 100644
index 2746d988..00000000
--- a/wrapperhelper/ast.h
+++ /dev/null
@@ -1,189 +0,0 @@
-#pragma once
-#include <clang/AST/ASTConsumer.h>
-#include <clang/AST/Attr.h>
-#include <clang/AST/Decl.h>
-#include <clang/AST/Type.h>
-#include <clang/Tooling/Tooling.h>
-#include <clang/Tooling/CompilationDatabase.h>
-#include <clang/AST/RecursiveASTVisitor.h>
-#include <clang/Frontend/FrontendAction.h>
-#include <clang/Frontend/CompilerInstance.h>
-#include <clang/Tooling/CommonOptionsParser.h>
-
-#include <ios>
-#include <llvm/Support/Casting.h>
-
-#include <cstddef>
-#include <fstream>
-#include <memory>
-#include <string>
-#include <iostream>
-#include <vector>
-#include <cstring>
-#include <map>
-
-#include "gen.h"
-#include "utils.h"
-
-static void ParseParameter(clang::ASTContext* AST, WrapperGenerator* Gen, clang::QualType ParmType, FuncInfo* Func) {
-    using namespace clang;
-    (void)AST; (void)Func;
-    if (ParmType->isFunctionPointerType()) {
-        auto ProtoType = ParmType->getPointeeType()->getAs<FunctionProtoType>();
-        for (unsigned i = 0; i < ProtoType->getNumParams(); i++) {
-            ParseParameter(AST, Gen, ProtoType->getParamType(i), Func);
-        }
-    } else if (ParmType->isPointerType()) {
-        auto PointeeType = ParmType->getPointeeType();
-        if (PointeeType->isRecordType()) {
-            if (Gen->records.find(StripTypedef(PointeeType)) == Gen->records.end()) {
-                auto Record = &Gen->records[StripTypedef(PointeeType)];
-                if (PointeeType->isUnionType()) {
-                    Record->is_union = true;
-                }
-                Record->type = StripTypedef(PointeeType);
-                Record->decl = PointeeType->getAs<RecordType>()->getDecl();
-                Record->type_name = Record->decl->getIdentifier() ? Record->decl->getIdentifier()->getName().str() : "<null identifier>";
-            }
-        } else if (PointeeType->isPointerType()) {
-            PointeeType = PointeeType->getPointeeType();
-            if (PointeeType->isRecordType()) {
-                if (Gen->records.find(StripTypedef(PointeeType)) == Gen->records.end()) {
-                    auto Record = &Gen->records[StripTypedef(PointeeType)];
-                    if (PointeeType->isUnionType()) {
-                        Record->is_union = true;
-                    }
-                    
-                    Record->type = StripTypedef(PointeeType);
-                    Record->decl = PointeeType->getAs<RecordType>()->getDecl();
-                    Record->type_name = Record->decl->getIdentifier() ? Record->decl->getIdentifier()->getName().str() : "<null identifier>";
-                }
-            }
-        }
-    } else if (ParmType->isRecordType()) {
-        if (Gen->records.find(StripTypedef(ParmType)) == Gen->records.end()) {
-            auto Record = &Gen->records[StripTypedef(ParmType)];
-            if (ParmType->isUnionType()) {
-                Record->is_union = true;
-            }
-            Record->type = StripTypedef(ParmType);
-            Record->decl = ParmType->getAs<RecordType>()->getDecl();
-            Record->type_name = Record->decl->getIdentifier() ? Record->decl->getIdentifier()->getName().str() : "<null identifier>";
-        }
-    }
-}
-
-static void ParseFunction(clang::ASTContext* AST, WrapperGenerator* Gen, clang::FunctionDecl* Decl) {
-    using namespace clang;
-    auto Type = Decl->getType().getTypePtr();
-    auto FuncInfo = &Gen->funcs[Type];
-    FuncInfo->type = Type;
-    FuncInfo->func_name = Decl->getNameAsString();
-    FuncInfo->decl = Decl;
-    FuncInfo->callback_args.resize(Decl->getNumParams());
-    if (Decl->getAttr<WeakRefAttr>()) {
-        FuncInfo->is_weak = true;
-    }
-    if (Decl->isVariadic()) {
-        FuncInfo->is_variadaic = true;
-    }
-    for (unsigned i = 0; i < Decl->getNumParams(); i++) {
-        auto ParmDecl = Decl->getParamDecl(i);
-        if (ParmDecl->getType()->isFunctionPointerType()) {
-            FuncInfo->callback_args[i] = ParmDecl->getType().getTypePtr();
-            FuncInfo->has_callback_arg = true;
-        } else {
-            FuncInfo->callback_args[i] = nullptr;
-        }
-        ParseParameter(AST, Gen, ParmDecl->getType(), FuncInfo);
-    }
-}
-
-class MyASTVisitor : public clang::RecursiveASTVisitor<MyASTVisitor> {
-public:
-    MyASTVisitor(clang::ASTContext* ctx) : Ctx(ctx) {}
-    MyASTVisitor(clang::ASTContext* ctx, WrapperGenerator* gen) : Ctx(ctx), Gen(gen) {}
-
-    bool VisitFunctionDecl(clang::FunctionDecl* Decl) {
-        ParseFunction(Ctx, Gen, Decl);
-        return true;
-    }
-private:
-    clang::ASTContext* Ctx;
-    WrapperGenerator* Gen;
-};
-
-class MyASTConsumer : public clang::ASTConsumer {
-public:
-    MyASTConsumer(clang::ASTContext* Context, const std::string& libname, const std::string& host_triple, const std::string& guest_triple)
-        : Visitor(Context, &Generator) {
-        Generator.Init(libname, host_triple, guest_triple);
-    }
-    void HandleTranslationUnit(clang::ASTContext &Ctx) override {
-        Visitor.TraverseDecl(Ctx.getTranslationUnitDecl());
-        std::cout << "--------------- Libclangtooling parse complete -----------------\n";
-        Generator.Prepare(&Ctx);
-        std::cout << "--------------- Generator prepare complete -----------------\n";
-        std::ofstream FuncDeclFile("wrapped" + Generator.libname + "_private.h", std::ios::out);
-        FuncDeclFile << Generator.GenFuncDeclare(&Ctx);
-        FuncDeclFile.close();
-        std::ofstream FuncDefineFile("wrapped" + Generator.libname + ".c", std::ios::out);
-        FuncDefineFile << "#include <stdio.h>\n"
-                          "#include <stdlib.h>\n"
-                          "#include <string.h>\n"
-                          "#define _GNU_SOURCE         /* See feature_test_macros(7) */\n"
-                          "#include <dlfcn.h>\n"
-                          "\n"
-                          "#include \"wrappedlibs.h\"\n"
-                          "\n"
-                          "#include \"debug.h\"\n"
-                          "#include \"wrapper.h\"\n"
-                          "#include \"bridge.h\"\n"
-                          "#include \"x64emu.h\"\n"
-                          "#include \"box64context.h\"\n"
-                          "\n"
-                          "const char* " + Generator.libname + "Name = \"" + Generator.libname + "\";\n"
-                          "#define LIBNAME " + Generator.libname + "\n"
-                          "\n"
-                          "#define ADDED_FUNCTIONS()           \\\n"
-                          "\n"
-                          "#include \"generated/wrapped" + Generator.libname + "types.h\"\n";
-        FuncDefineFile << Generator.GenRecordDeclare(&Ctx);
-        FuncDefineFile << Generator.GenRecordConvert(&Ctx);
-        FuncDefineFile << Generator.GenCallbackWrap(&Ctx);
-        FuncDefineFile << Generator.GenFuncDefine(&Ctx);
-        FuncDefineFile.close();
-        std::cout << "--------------- Generator gen complete -----------------\n";
-    }
-private:
-    MyASTVisitor Visitor;
-    WrapperGenerator Generator;
-};
-
-class MyGenAction : public clang::ASTFrontendAction {
-public:
-    MyGenAction(const std::string& libname, const std::string& host_triple, const std::string& guest_triple) :
-        libname(libname), host_triple(host_triple), guest_triple(guest_triple)  {}
-    std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance& Compiler, clang::StringRef file) override {
-        (void)file;
-        return std::make_unique<MyASTConsumer>(&Compiler.getASTContext(), libname, host_triple, guest_triple);
-    }
-private:
-    std::string libname;
-    std::string host_triple;
-    std::string guest_triple;
-};
-
-class MyFrontendActionFactory : public clang::tooling::FrontendActionFactory {
-public:
-    MyFrontendActionFactory(const std::string& libname, const std::string& host_triple, const std::string& guest_triple) : 
-        libname(libname), host_triple(host_triple), guest_triple(guest_triple)  {}
-private:
-    std::unique_ptr<clang::FrontendAction> create() override {
-        return std::make_unique<MyGenAction>(libname, host_triple, guest_triple);
-    }
-private:
-    std::string libname;
-    std::string host_triple;
-    std::string guest_triple;
-};
diff --git a/wrapperhelper/example-libc.h b/wrapperhelper/example-libc.h
new file mode 100644
index 00000000..24dea13d
--- /dev/null
+++ b/wrapperhelper/example-libc.h
@@ -0,0 +1,170 @@
+#define __x86_64__
+#define __WCHAR_MAX__ 2147483647
+#define __WCHAR_MIN__ (-__WCHAR_MAX - 1)
+#define _GNU_SOURCE 1
+#define __USE_MISC 1
+#define PORTMAP
+#define __WORDSIZE 64
+
+// Based on /usr/include/clang/Basic/TokenKinds.def
+// Alternate spelling for various tokens.  There are GCC extensions in all
+// languages, but should not be disabled in strict conformance mode.
+#define __alignof__   __alignof
+#define __asm         asm
+#define __asm__       asm
+#define __complex     _Complex
+#define __complex__   _Complex
+#define __const       const
+#define __const__     const
+#define __decltype    decltype
+#define __imag__      __imag
+#define __inline      inline
+#define __inline__    inline
+#define __nullptr     nullptr
+#define __real__      __real
+#define __restrict    restrict
+#define __restrict__  restrict
+#define __signed      signed
+#define __signed__    signed
+#define __typeof      typeof
+#define __typeof__    typeof
+#define __volatile    volatile
+#define __volatile__  volatile
+
+typedef __int128 __int128_t;
+typedef unsigned __int128 __uint128_t;
+
+// TODO
+#define inline
+
+#include <stdint.h>
+
+#include <aliases.h>
+#include <argp.h>
+#include <argz.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <asm/prctl.h>
+#include <complex.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <envz.h>
+#include <err.h>
+#include <error.h>
+#include <execinfo.h>
+#include <event.h>
+#include <fcntl.h>
+#include <fmtmsg.h>
+#include <fnmatch.h>
+#include <fstab.h>
+#include <fts.h>
+#include <ftw.h>
+#include <gconv.h>
+#include <glob.h>
+#include <gnu/libc-version.h>
+#include <grp.h>
+#include <gshadow.h>
+#include <iconv.h>
+#include <ifaddrs.h>
+#include <inttypes.h>
+#include <langinfo.h>
+#include <libgen.h>
+#include <libintl.h>
+#include <link.h>
+#include <linux/module.h>
+#include <locale.h>
+#include <math.h>
+#include <malloc.h>
+#include <mcheck.h>
+#include <mntent.h>
+#include <monetary.h>
+#include <netdb.h>
+#include <net/if.h>
+#include <netinet/ether.h>
+#include <netinet/in.h>
+#include <nl_types.h>
+#include <obstack.h>
+#include <poll.h>
+#include <printf.h>
+#include <pwd.h>
+#include <regex.h>
+#include <resolv.h>
+#include <rpc/auth.h>
+#include <rpc/des_crypt.h>
+#include <rpc/key_prot.h>
+#include <rpc/rpc.h>
+#include <sched.h>
+#include <search.h>
+#include <setjmp.h>
+#include <shadow.h>
+#include <signal.h>
+#include <spawn.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/auxv.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+#include <sys/fanotify.h>
+#include <sys/file.h>
+#include <sys/fsuid.h>
+#include <sys/inotify.h>
+#include <sys/io.h>
+#include <sys/ipc.h>
+#include <sys/klog.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/msg.h>
+#include <sys/personality.h>
+#include <sys/prctl.h>
+#include <sys/profil.h>
+#include <sys/ptrace.h>
+#include <sys/quota.h>
+#include <sys/random.h>
+#include <sys/reboot.h>
+#include <sys/resource.h>
+#include <sys/sem.h>
+#include <sys/sendfile.h>
+#include <sys/shm.h>
+#include <sys/signal.h>
+#include <sys/signalfd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <sys/statvfs.h>
+#include <sys/swap.h>
+#include <sys/syscall.h>
+#include <sys/sysinfo.h>
+#include <sys/syslog.h>
+#include <sys/sysmacros.h>
+#include <sys/time.h>
+#include <sys/timeb.h>
+#include <sys/timerfd.h>
+#include <sys/times.h>
+#include <sys/timex.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/vfs.h>
+#include <sys/xattr.h>
+#include <syslog.h>
+#include <termios.h>
+#include <time.h>
+#include <threads.h>
+#include <ttyent.h>
+#include <uchar.h>
+#include <ucontext.h>
+#include <ulimit.h>
+#include <unistd.h>
+#include <unistdio.h>
+#include <utime.h>
+#include <utmp.h>
+#include <utmpx.h>
+#include <wait.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <wordexp.h>
+
+#pragma wrappers explicit_simple FTS
+#pragma wrappers explicit_simple FTS64
+#pragma wrappers explicit_simple glob_t
+#pragma wrappers explicit_simple glob64_t
diff --git a/wrapperhelper/gen.cpp b/wrapperhelper/gen.cpp
deleted file mode 100644
index 743474cd..00000000
--- a/wrapperhelper/gen.cpp
+++ /dev/null
@@ -1,1059 +0,0 @@
-#include "gen.h"
-#include "utils.h"
-#include <clang/AST/ASTContext.h>
-#include <clang/AST/Decl.h>
-#include <clang/AST/Type.h>
-#include <clang/Basic/LLVM.h>
-
-using namespace clang;
-using namespace clang::tooling;
-
-static std::vector<uint64_t> GetRecordFieldOff(const std::string& Code, const std::string& Triple) {
-    std::vector<uint64_t> FieldOff;
-    std::vector<std::string> Args = {"-target", Triple};
-    std::unique_ptr<clang::ASTUnit> AST = clang::tooling::buildASTFromCodeWithArgs(Code, Args);
-    auto& Ctx = AST->getASTContext();
-    auto TranslateDecl = Ctx.getTranslationUnitDecl();
-    for (const auto& Decl : TranslateDecl->decls()) {
-        if (const auto RecordDecl = clang::dyn_cast<clang::RecordDecl>(Decl)) {
-            auto& RecordLayout = Ctx.getASTRecordLayout(RecordDecl);
-            for (unsigned i = 0; i < RecordLayout.getFieldCount(); i++) {
-                FieldOff.push_back(RecordLayout.getFieldOffset(i) / 8);
-            }
-            break;
-        }
-    }
-    return FieldOff;
-}
-
-static uint64_t GetRecordSize(const std::string& Code, const std::string& Triple) {
-    std::vector<std::string> Args = {"-target", Triple};
-    std::unique_ptr<ASTUnit> AST = buildASTFromCodeWithArgs(Code, Args);
-    auto& Ctx = AST->getASTContext();
-    auto TranslateDecl = Ctx.getTranslationUnitDecl();
-    for (const auto& Decl : TranslateDecl->decls()) {
-        if (const auto recordDecl = dyn_cast<RecordDecl>(Decl)) {
-            return Ctx.getTypeSize(recordDecl->getTypeForDecl()) / 8;
-        }
-    }
-    return 0;
-}
-
-static CharUnits::QuantityType GetRecordAlign(const std::string& Code, const std::string& Triple) {
-    std::vector<std::string> Args = {"-target", Triple};
-    std::unique_ptr<ASTUnit> AST = buildASTFromCodeWithArgs(Code, Args);
-    auto& Ctx = AST->getASTContext();
-    auto TranslateDecl = Ctx.getTranslationUnitDecl();
-    for (const auto& Decl : TranslateDecl->decls()) {
-        if (const auto recordDecl = dyn_cast<RecordDecl>(Decl)) {
-            auto& RecordLayout = Ctx.getASTRecordLayout(recordDecl);
-            for (unsigned i = 0; i < RecordLayout.getFieldCount(); i++) {
-                return RecordLayout.getAlignment().getQuantity() / 8;
-            }
-            break;
-        }
-    }
-    return 0;
-}
-
-static uint64_t GetTypeSize(const Type* Type, const std::string& Triple) {
-    std::string Code = Type->getCanonicalTypeInternal().getAsString() + " dummy;";
-    std::vector<std::string> Args = {"-target", Triple};
-    std::unique_ptr<ASTUnit> AST = buildASTFromCodeWithArgs(Code, Args);
-    auto& Ctx = AST->getASTContext();
-    auto TranslateDecl = Ctx.getTranslationUnitDecl();
-    for (const auto& Decl : TranslateDecl->decls()) {
-        if (const auto varDecl = dyn_cast<VarDecl>(Decl)) {
-            return Ctx.getTypeSize(varDecl->getType()) / 8;
-        }
-    }
-    return 0;
-}
-
-static std::string TypeToSig(ASTContext* Ctx, const Type* Type) {
-    if (Type->isBuiltinType()) {
-        switch (Type->getAs<clang::BuiltinType>()->getKind()) {
-        case clang::BuiltinType::Kind::Void:
-            return "v";
-        case clang::BuiltinType::Kind::Bool:
-            return "i";
-        case clang::BuiltinType::Kind::Char_U:
-            return "C";
-        case clang::BuiltinType::Kind::Char_S:
-            return "c";
-        case clang::BuiltinType::Kind::Char8:
-            return "c";
-        case clang::BuiltinType::Kind::UChar:
-            return "C";
-        case clang::BuiltinType::Kind::SChar:
-            return "c";
-        case clang::BuiltinType::Kind::WChar_U:
-            return "W";
-        case clang::BuiltinType::Kind::UShort:
-            return "W";
-        case clang::BuiltinType::Kind::WChar_S:
-            return "w";
-        case clang::BuiltinType::Kind::Char16:
-            return "w";
-        case clang::BuiltinType::Kind::Short:
-            return "w";
-        case clang::BuiltinType::Kind::UInt:
-            return "u";
-        case clang::BuiltinType::Kind::Char32:
-            return "i";
-        case clang::BuiltinType::Kind::Int:
-            return "i";
-        case clang::BuiltinType::Kind::ULong:
-            return "L";
-        case clang::BuiltinType::Kind::Long:
-            return "l";
-        case clang::BuiltinType::Kind::ULongLong:
-            return "U";
-        case clang::BuiltinType::Kind::LongLong:
-            return "I";
-        case clang::BuiltinType::Kind::UInt128:
-            return "H";
-        case clang::BuiltinType::Kind::Int128:
-            return "H";
-        case clang::BuiltinType::Kind::Float:
-            return "f";
-        case clang::BuiltinType::Kind::Double:
-            return "d";
-        case clang::BuiltinType::Kind::LongDouble:
-            return "D";
-        case clang::BuiltinType::Kind::NullPtr:
-            return "p"; // nullptr_t
-        default:
-            std::cout << "Unsupported BuiltinType: " << Type->getCanonicalTypeInternal().getAsString() << std::endl;
-        }
-    } else {
-        if (Type->isPointerType()) {
-            return "p";
-        } else if (Type->isVoidType()) {
-            return "v";
-        } else if (Type->isUnsignedIntegerOrEnumerationType()) {
-            switch(Ctx->getTypeSizeInChars(Type).getQuantity()) {
-                case 1:
-                    return "C";
-                case 2:
-                    return  "W";
-                case 4:
-                    return "u";
-                case 8:
-                    return "U";
-                default:
-                    std::cout << "Unsupported UnsignedInteger Type: " << Type->getCanonicalTypeInternal().getAsString() << std::endl;
-            }
-        } else if (Type->isSignedIntegerOrEnumerationType()) {
-            switch(Ctx->getTypeSizeInChars(Type).getQuantity()) {
-                case 1:
-                    return "c";
-                case 2:
-                    return  "w";
-                case 4:
-                    return "i";
-                case 8:
-                    return "I";
-                default:
-                    std::cout << "Unsupported SignedInteger Type: " << Type->getCanonicalTypeInternal().getAsString() << std::endl;
-            }
-        } else if (Type->isCharType()) {
-            return "c";
-        } else if (Type->isFloatingType()) {
-            switch(Ctx->getTypeSizeInChars(Type).getQuantity()) {
-                case 4:
-                    return "f";
-                case 8:
-                    return "d";
-                case 16:
-                    return "D";
-                default:
-                    std::cout << "Unsupported Floating Type: " << Type->getCanonicalTypeInternal().getAsString()
-                              << " (quantity = " << Ctx->getTypeSizeInChars(Type).getQuantity() << ")" << std::endl;
-            }
-        } else {
-            std::cout << "Unsupported Type: " << Type->getCanonicalTypeInternal().getAsString() << std::endl;
-        }
-    }
-    return "?";
-}
-
-// Prepare for generation, collect the structures and functions that need to be prcessed
-void WrapperGenerator::Prepare(ASTContext *Ctx) {
-  for (const auto &func_pair : funcs) {
-    for (auto Type : func_pair.second.callback_args) {
-      if (Type && Type->isTypedefNameType()) {
-        callbacks[StripTypedef(Type->getPointeeType())] =
-            Type->getAs<TypedefType>()->getDecl()->getNameAsString();
-      } else if (Type) {
-        callbacks[StripTypedef(Type->getPointeeType())] =
-            GetFuncSig(Ctx, Type->getPointeeType().getTypePtr()) + "_t";
-      }
-    }
-  }
-  std::vector<const Type *> Types;
-  for (const auto &record_pair : records) {
-    Types.push_back(record_pair.first);
-  }
-  for (auto type : Types) {
-    std::set<const Type*> Visited{type};
-    bool Special = false;
-    ParseRecordRecursive(Ctx, type, Special, Visited);
-  }
-  for (auto it = records.begin(); it != records.end();) {
-    if (!it->second.is_special) {
-      it = records.erase(it);
-    } else {
-      for (auto field : it->second.callback_fields) {
-        if (field->isTypedefNameType()) {
-          callbacks[StripTypedef(field->getPointeeType())] =
-              field->getAs<TypedefType>()->getDecl()->getNameAsString();
-        } else {
-          callbacks[StripTypedef(field->getPointeeType())] =
-              GetFuncSig(Ctx, field->getPointeeType().getTypePtr()) + "_t";
-        }
-      }
-      ++it;
-    }
-  }
-  for (auto &func_pair : funcs) {
-    for (unsigned i = 0; i < func_pair.second.decl->getNumParams(); i++) {
-      auto ParamDecl = func_pair.second.decl->getParamDecl(i);
-      auto ParamType = ParamDecl->getType();
-      if (ParamType->isPointerType() &&
-          ParamType->getPointeeType()->isRecordType()) {
-        if (records.find(StripTypedef(ParamType->getPointeeType())) !=
-            records.end()) {
-          func_pair.second.has_special_arg = true;
-          break;
-        }
-      } else if (ParamType->isRecordType()) {
-        if (records.find(StripTypedef(ParamType)) != records.end()) {
-          func_pair.second.has_special_arg = true;
-          break;
-        }
-      }
-    }
-    auto RetType = func_pair.second.decl->getReturnType();
-    if (RetType->isPointerType() && RetType->getPointeeType()->isRecordType()) {
-      if (records.find(StripTypedef(RetType->getPointeeType())) !=
-          records.end()) {
-        func_pair.second.has_special_ret = true;
-      }
-    } else if (RetType->isRecordType()) {
-      if (records.find(StripTypedef(RetType)) != records.end()) {
-        func_pair.second.has_special_ret = true;
-      }
-    }
-  }
-}
-
-// Gen callback typedef
-std::string WrapperGenerator::GenCallbackTypeDefs(ASTContext *Ctx) {
-  (void)Ctx;
-  std::string res;
-  for (auto callback : callbacks) {
-    auto Type = callback.first;
-    auto Definition = GetFuncDefinition(Type);
-    res += "typedef " + Definition.ret_str + "(*" + callback.second + ")(";
-    for (int i = 0; i < Definition.arg_size - 1; i++) {
-      res += Definition.arg_types_str[i] + Definition.arg_names[i] + ", ";
-    }
-    if (Definition.arg_size) {
-      res += Definition.arg_types_str[Definition.arg_size - 1] + Definition.arg_names[Definition.arg_size - 1];
-    }
-    res += ");\n";
-  }
-  return res;
-}
-
-// Gen function declare
-std::string WrapperGenerator::GenDeclare(ASTContext *Ctx,
-                                         const FuncInfo &Func) {
-  std::string res;
-  std::string sig = GetFuncSig(Ctx, Func);
-  res += "GO";
-  if (Func.is_weak) {
-    res += "W";
-  }
-  if (sig.find('E') != std::string::npos) {
-    res += "M";
-  }
-  res += "(" + Func.func_name + ", " + sig + ")\n";
-  ;
-  return res;
-}
-
-// Gen structure declare
-std::string WrapperGenerator::GenDeclare(ASTContext *Ctx,
-                                         const RecordInfo &Record) {
-  (void)Ctx;
-  std::string RecordStr;
-  std::string PreDecl;
-  RecordStr += "\ntypedef ";
-  RecordStr +=
-      (Record.is_union ? "union " : "struct ") + Record.type_name + " {\n";
-  for (const auto &Field : Record.decl->fields()) {
-    auto Type = Field->getType();
-    std::string Name = Field->getNameAsString();
-    RecordStr += "    ";
-    if (Type->isFunctionPointerType()) {
-      auto FuncType = StripTypedef(Type->getPointeeType());
-      if (callbacks.count(FuncType)) {
-        std::string FieldStr = callbacks[FuncType];
-        FieldStr += " ";
-        FieldStr += Name;
-        RecordStr += FieldStr;
-      } else {
-        std::cout << "Err: FuncPtr(" << Record.type_name << "." << Name << ") is not supported\n";
-      }
-    } else if (Type->isPointerType()) {
-      auto PointeeType = Type->getPointeeType();
-      if (PointeeType->isRecordType()) {
-        if (records.count(PointeeType.getTypePtr())) {
-          std::string FieldStr = records[PointeeType.getTypePtr()].type_name;
-          FieldStr += "_ptr ";
-          FieldStr += Name;
-          RecordStr += FieldStr;
-        } else {
-          RecordStr += "void *" + Name;
-        }
-      } else {
-        RecordStr += "void *" + Name;
-      }
-    } else if (Type->isRecordType()) {
-      if (records.count(Type.getTypePtr())) {
-        std::string FieldStr = records[Type.getTypePtr()].type_name;
-        FieldStr += " ";
-        FieldStr += Name;
-        RecordStr += FieldStr;
-      } else {
-        RecordStr += TypeStringify(StripTypedef(Type), Field, nullptr, PreDecl);
-      }
-    } else {
-      RecordStr += TypeStringify(StripTypedef(Type), Field, nullptr, PreDecl);
-    }
-    RecordStr += ";\n";
-  }
-  RecordStr += "} ";
-  RecordStr += Record.type_name + ", *" + Record.type_name + "_ptr;\n";
-  return RecordStr;
-}
-
-std::string WrapperGenerator::GenCallbackWrap(ASTContext *Ctx,
-                                              const FuncInfo &Func) {
-  (void)Ctx;
-  std::string res;
-
-  for (unsigned i = 0; i < Func.decl->getNumParams(); i++) {
-    auto ParamType = Func.decl->getParamDecl(i)->getType();
-    if (ParamType->isFunctionPointerType()) {
-
-      auto PointeeType = ParamType->getPointeeType();
-      auto Definition = GetFuncDefinition(PointeeType.getTypePtr());
-      std::string my_funcname =
-          std::string("my_") + Func.decl->getParamDecl(i)->getNameAsString();
-      std::string funcname = Func.decl->getParamDecl(i)->getNameAsString();
-      res += "\n#define GO(A) \\\n"
-             "static uintptr_t " + my_funcname + "_fct_##A = 0; \\\n" +
-             Definition.ret_str + " " + my_funcname + "(";
-      int arg_size = Definition.arg_names.size();
-      if (arg_size) {
-        for (int i = 0; i < arg_size - 1; i++) {
-          res += Definition.arg_types_str[i] + " " + Definition.arg_names[i] + ", ";
-        }
-        res += Definition.arg_types_str[arg_size - 1] + " " + Definition.arg_names[arg_size - 1];
-      }
-      res += ") { \\\n"
-             "    return RunFunction(my_context, " + my_funcname + "_fct_##A" + ", " + std::to_string(arg_size);
-      for (int i = 0; i < arg_size; i++) {
-        res += ", " + Definition.arg_names[i];
-      }
-      res += "); \\\n"
-             "}\n"
-             "SUPER()\n"
-             "#undef GO\n"
-             "static void* find" + funcname + "Fct(void* fct) {\n"
-             "    if (!fct) return fct;\n"
-             "    if (GetNativeFnc((uintptr_t)fct)) return GetNativeFnc((uintptr_t)fct);\n"
-             "    #define GO(A) if (" + my_funcname + "_fct_##A == (uintptr_t)fct) return " + my_funcname + "_##A;\n"
-             "    SUPER()\n"
-             "    #undef GO\n"
-             "    #define GO(A) if (" + my_funcname + "_fct_##A == 0) { " + my_funcname + "_fct_##A = (uintptr_t)fct; return " + my_funcname + "_##A; }\n"
-             "    SUPER()\n"
-             "    #undef GO\n"
-             "    return NULL;\n"
-             "}\n";
-    }
-  }
-  return res;
-}
-
-std::string WrapperGenerator::GenCallbackWrap(ASTContext *Ctx,
-                                              const RecordInfo &Struct) {
-  (void)Ctx;
-  std::string res;
-  for (const auto &field : Struct.decl->fields()) {
-    auto FieldType = field->getType();
-    if (FieldType->isFunctionPointerType()) {
-      auto PointeeType = FieldType->getPointeeType();
-      auto Definition = GetFuncDefinition(PointeeType.getTypePtr());
-      std::string my_funcname = std::string("my_") + field->getNameAsString();
-      std::string funcname = field->getNameAsString();
-      res += "\n#define GO(A) \\\n"
-             "static uintptr_t " + my_funcname + "_fct_##A = 0; \\\n" +
-             Definition.ret_str + " " + my_funcname + "_##A(";
-      int arg_size = Definition.arg_names.size();
-      if (arg_size) {
-        for (int i = 0; i < arg_size - 1; i++) {
-          res += Definition.arg_types_str[i] + " " + Definition.arg_names[i] + ", ";
-        }
-        res += Definition.arg_types_str[arg_size - 1] + " " + Definition.arg_names[arg_size - 1];
-      }
-      res += ") { \\\n"
-             "    return RunFunction(my_context, " + my_funcname + "_fct_##A" + ", " + std::to_string(arg_size);
-      for (int i = 0; i < arg_size; i++) {
-        res += ", " + Definition.arg_names[i];
-      }
-      res += "); \\\n"
-             "}\n"
-             "SUPER()\n"
-             "#undef GO\n"
-             "static void* find" + funcname + "Fct(void* fct) {\n"
-             "    if(!fct) return fct;\n"
-             "    if(GetNativeFnc((uintptr_t)fct)) return GetNativeFnc((uintptr_t)fct);\n"
-             "    #define GO(A) if(" + my_funcname + "_fct_##A == (uintptr_t)fct) return " + my_funcname + "_##A;}\n"
-             "    SUPER()\n"
-             "    #undef GO\n"
-             "    #define GO(A) if(" + my_funcname + "_fct_##A == 0) {" + my_funcname + "_fct_##A = (uintptr_t)fct;" + "return " + my_funcname + "_##A;}\n"
-             "    SUPER()\n"
-             "    #undef GO\n"
-             "    return NULL;\n"
-             "}\n";
-    }
-  }
-  return res;
-}
-
-std::string WrapperGenerator::GenDefine(ASTContext *Ctx,
-                                        const FuncInfo &Func) {
-  std::string res;
-  auto Definition = GetFuncDefinition(Func.decl);
-  std::string Sig = GetFuncSig(Ctx, Func.type);
-  res += "\nEXPORT " + Definition.ret_str + "my_" + Func.func_name + "(";
-  if (Sig.find('E')) {
-    res += "void *emu, ";
-  }
-  int arg_size = Definition.arg_names.size();
-  if (arg_size) {
-    for (int i = 0; i < arg_size - 1; i++) {
-      if (Definition.arg_types[i]->isPointerType()) {
-        auto PointeeType = Definition.arg_types[i]->getPointeeType();
-        if (records.count(PointeeType.getTypePtr())) {
-          res +=
-              Definition.arg_types[i]->getCanonicalTypeInternal().getAsString();
-        } else {
-          res += Definition.arg_types_str[i];
-        }
-      } else {
-        res += Definition.arg_types_str[i];
-      }
-      res += " " + Definition.arg_names[i] + ", ";
-    }
-    if (Definition.arg_types[arg_size - 1]->isPointerType()) {
-      auto PointeeType = Definition.arg_types[arg_size - 1]->getPointeeType();
-      if (records.count(PointeeType.getTypePtr())) {
-        res += Definition.arg_types[arg_size - 1]
-                   ->getCanonicalTypeInternal()
-                   .getAsString();
-      } else {
-        res += Definition.arg_types_str[arg_size - 1];
-      }
-    } else {
-      res += Definition.arg_types_str[arg_size - 1];
-    }
-    res += " ";
-    res += Definition.arg_names[arg_size - 1];
-  }
-  res += ") {\n";
-  if (Func.has_special_arg) {
-    res += "    // WARN: This function's arg has a structure ptr which is "
-                   "special, may need to wrap it for the host\n";
-
-  } else if (Func.has_special_ret) {
-    res += "    // WARN: This function's ret is a structure ptr which is "
-                   "special, may need to wrap it for the guest\n";
-  }
-  if (Func.has_callback_arg) {
-    res += "    " + my_lib_type + " *my = " + "(" + my_lib_type + "*)" +
-                   my_lib + "->priv.w.p2;\n"
-                   "    my->" + Func.func_name + "(";
-    if (arg_size) {
-      for (int i = 0; i < arg_size - 1; i++) {
-        if (Func.callback_args[i]) {
-          if (!Func.callback_args[i]->isTypedefNameType()) {
-            res +=
-                "find" + Func.func_name + "_arg" + std::to_string(i) + "Fct";
-          } else {
-            res += "find" +
-                           Func.callback_args[i]
-                               ->getAs<TypedefType>()
-                               ->getDecl()
-                               ->getNameAsString() +
-                           "Fct";
-          }
-          res += "(" + Definition.arg_names[i] + ")";
-        } else {
-          res += Definition.arg_names[i];
-        }
-        res += ", ";
-      }
-      if (Func.callback_args[arg_size - 1]) {
-        if (!Func.callback_args[arg_size - 1]->isTypedefNameType()) {
-          res += "find" + Func.func_name + "_arg" +
-                         std::to_string(arg_size - 1) + "Fct";
-        } else {
-          res += "find" +
-                         Func.callback_args[arg_size - 1]
-                             ->getAs<TypedefType>()
-                             ->getDecl()
-                             ->getNameAsString() +
-                         "Fct";
-        }
-        res += "(" + Definition.arg_names[arg_size - 1] + ")";
-      } else {
-        res += Definition.arg_names[arg_size - 1];
-      }
-      res += ")\n";
-    }
-  } else {
-    res += "    " + my_lib_type + " *my = (" + my_lib_type + "*)" + my_lib + "->priv.w.p2;\n"
-                   "    my->" + Func.func_name + "(";
-    if (arg_size) {
-      for (int i = 0; i < arg_size - 1; i++) {
-        res += Definition.arg_names[i] + ", ";
-      }
-      res += Definition.arg_names[arg_size - 1];
-    }
-      res += ");\n";
-  }
-
-  res += "}\n";
-  return res;
-}
-
-std::string WrapperGenerator::GenDeclareDiffTriple(
-    ASTContext *Ctx, const RecordInfo &Record,
-    const std::string &GuestTriple, const std::string &HostTriple) {
-  (void)Ctx;
-  std::string GuestRecord;
-  std::string HostRecord;
-  std::string PreDecl;
-  std::vector<uint64_t> GuestFieldOff;
-  std::vector<uint64_t> HostFieldOff;
-  GuestRecord += "typedef ";
-  HostRecord += "typedef ";
-  GuestRecord +=
-      (Record.is_union ? "union " : "struct ") + Record.type_name + " {\n";
-  HostRecord += (Record.is_union ? "union " : "struct ") +
-                std::string("host_") + Record.type_name + " {\n";
-  auto OffDiff = GetRecordFieldOffDiff(Record.type, GuestTriple, HostTriple,
-                                       GuestFieldOff, HostFieldOff);
-  uint64_t GuestRecordSize = GetRecordSize(Record.type, GuestTriple);
-  uint64_t HostRecordSize = GetRecordSize(Record.type, HostTriple);
-  uint64_t SizeDiff = GuestRecordSize - HostRecordSize;
-  int FieldIndex = 0;
-  std::set<FieldDecl *> AlignDiffFields;
-  for (const auto &Field : Record.decl->fields()) {
-    if (OffDiff[FieldIndex] == 0) {
-      FieldIndex++;
-      continue;
-    }
-    std::string Name = Field->getNameAsString();
-    if (OffDiff[FieldIndex] != SizeDiff) {
-      auto Diff = OffDiff[FieldIndex];
-      AlignDiffFields.insert(Field);
-      for (size_t i = FieldIndex; i < OffDiff.size(); i++) {
-        if (OffDiff[i] == Diff) {
-          OffDiff[i] = 0;
-        } else {
-          break;
-        }
-      }
-    } else {
-      AlignDiffFields.insert(Field);
-      break;
-    }
-    FieldIndex++;
-  }
-  for (const auto &Field : Record.decl->fields()) {
-    auto Type = Field->getType();
-    std::string Name = Field->getNameAsString();
-    GuestRecord += "  ";
-    HostRecord += "  ";
-    if (AlignDiffFields.find(Field) != AlignDiffFields.end()) {
-      auto typeSize = GetTypeSize(StripTypedef(Field->getType()), guest_triple);
-      switch (typeSize) {
-      // FIXME: should test more case in different triple
-      case 4: GuestRecord += "int " + Name        ; break;
-      case 8: GuestRecord += "int " + Name + "[2]"; break;
-      default:
-        std::cout << "Err: unknown type size " << typeSize << std::endl;
-        break;
-      }
-      HostRecord += TypeStringify(StripTypedef(Type), Field, nullptr, PreDecl);
-    } else if (Type->isFunctionPointerType()) {
-      auto FuncType = StripTypedef(Type->getPointeeType());
-      if (callbacks.count(FuncType)) {
-        std::string FieldStr = callbacks[FuncType];
-        FieldStr += " ";
-        FieldStr += Name;
-        GuestRecord += FieldStr;
-        HostRecord += FieldStr;
-      } else {
-        std::cout << "Err: FuncPtr(" << Record.type_name << "." << Name << ") is not supported" << std::endl;
-      }
-    } else if (Type->isPointerType()) {
-      auto PointeeType = Type->getPointeeType();
-      if (PointeeType->isRecordType()) {
-        if (records.count(PointeeType.getTypePtr())) {
-          std::string FieldStr = records[PointeeType.getTypePtr()].type_name;
-          FieldStr += "_ptr " + Name;
-          GuestRecord += FieldStr;
-          HostRecord += "host_" + FieldStr;
-        } else {
-          GuestRecord += "void *" + Name;
-          HostRecord += "void *" + Name;
-        }
-      } else {
-        GuestRecord += "void *" + Name;
-        HostRecord += "void *" + Name;
-      }
-    } else if (Type->isRecordType()) {
-      if (records.count(Type.getTypePtr())) {
-        std::string FieldStr = records[Type.getTypePtr()].type_name;
-        FieldStr += " " + Name;
-        GuestRecord += FieldStr;
-        HostRecord += "host_" + FieldStr;
-      } else {
-        GuestRecord += TypeStringify(StripTypedef(Type), Field, nullptr, PreDecl);
-        HostRecord += TypeStringify(StripTypedef(Type), Field, nullptr, PreDecl);
-      }
-    } else {
-      HostRecord += TypeStringify(StripTypedef(Type), Field, nullptr, PreDecl);
-      GuestRecord += TypeStringify(StripTypedef(Type), Field, nullptr, PreDecl);
-    }
-    GuestRecord += ";\n";
-    HostRecord += ";\n";
-  }
-  GuestRecord += "} " + Record.type_name + ", *" + Record.type_name + "_ptr;\n";
-  HostRecord += "} host_" + Record.type_name + ", *host_" + Record.type_name + "_ptr;\n";
-  return GuestRecord + HostRecord;
-}
-
-// Gen record convert function between host and guest
-std::string WrapperGenerator::GenRecordConvert(const RecordInfo &Record) {
-  std::string res;
-  if (Record.guest_size != Record.host_size) {
-    auto RecordDecl = Record.decl;
-    std::vector<uint64_t> GuestFieldOff;
-    std::vector<uint64_t> HostFieldOff;
-    auto OffDiff = GetRecordFieldOffDiff(Record.type, guest_triple, host_triple,
-                                         GuestFieldOff, HostFieldOff);
-    int FieldIndex = 0;
-    std::vector<FieldDecl *> AlignDiffFields;
-    uint64_t SizeDiff = Record.guest_size - Record.host_size;
-    for (const auto &Field : RecordDecl->fields()) {
-      if (OffDiff[FieldIndex] == 0) {
-        FieldIndex++;
-        continue;
-      }
-      if (OffDiff[FieldIndex] != SizeDiff) {
-        auto Diff = OffDiff[FieldIndex];
-        AlignDiffFields.push_back(Field);
-        for (size_t i = FieldIndex; i < OffDiff.size(); i++) {
-          if (OffDiff[i] == Diff) {
-            OffDiff[i] = 0;
-          } else {
-            break;
-          }
-        }
-      } else {
-        AlignDiffFields.push_back(Field);
-        break;
-      }
-      FieldIndex++;
-    }
-    if (!AlignDiffFields.size()) {
-      return res;
-    }
-    res += "void g2h_" + Record.type_name + "(" + "struct host_" + Record.type_name + " *d, struct " + Record.type_name + " *s) {\n";
-    std::string body = "    memcpy(d, s, offsetof(struct " + Record.type_name +
-                       ", " + AlignDiffFields[0]->getNameAsString() + "));\n";
-    std::string offstr = "offsetof(struct " + Record.type_name + ", " +
-                         AlignDiffFields[0]->getNameAsString() + ")";
-    for (size_t i = 0; i < AlignDiffFields.size() - 1; i++) {
-      body += "    memcpy(d->" + AlignDiffFields[i]->getNameAsString() + ", " +
-              "s->" + AlignDiffFields[i]->getNameAsString() + ", " +
-              "offsetof(struct " + Record.type_name + ", " +
-              AlignDiffFields[i + 1]->getNameAsString() + ") - " + offstr +
-              ");\n";
-      offstr = "offsetof(struct " + Record.type_name + ", " +
-               AlignDiffFields[i + 1]->getNameAsString() + ")";
-    }
-    body += "    memcpy(d->" +
-            AlignDiffFields[AlignDiffFields.size() - 1]->getNameAsString() +
-            ", " + "s->" +
-            AlignDiffFields[AlignDiffFields.size() - 1]->getNameAsString() +
-            ", " + std::to_string(GetRecordSize(Record.type, guest_triple)) +
-            " - " + offstr + ");\n";
-    res += body + "}\n";
-
-    res += "void h2g_" + Record.type_name + "(struct " + Record.type_name + " *d, " + "struct host_" + Record.type_name + " *s) {\n";
-    res += body;
-    res += "}\n";
-  }
-  return res;
-}
-
-void WrapperGenerator::ParseRecordRecursive(
-    ASTContext *Ctx, const Type *type, bool &Special,
-    std::set<const Type *> &Visited) {
-  auto recordType = type->getAs<RecordType>();
-  auto RecordDecl = recordType->getDecl();
-  for (const auto &field : RecordDecl->fields()) {
-    auto FieldType = field->getType();
-    if (FieldType->isFunctionPointerType()) {
-      auto Record = &records[type];
-      Record->callback_fields.push_back(field->getType().getTypePtr());
-      // Record->type_name =
-      Special = true;
-    } else if (FieldType->isPointerType() &&
-               FieldType->getPointeeType()->isRecordType()) {
-      auto FieldRecordType = StripTypedef(FieldType->getPointeeType());
-      if (Visited.find(FieldRecordType) != Visited.end())
-        continue;
-      Visited.insert(FieldRecordType);
-      bool _Special = false;
-      ParseRecordRecursive(Ctx, FieldRecordType, _Special, Visited);
-      if (_Special)
-        Special = true;
-    } else if (FieldType->isRecordType()) {
-      auto FieldRecordType = StripTypedef(FieldType);
-      if (Visited.find(FieldRecordType) != Visited.end())
-        continue;
-      Visited.insert(FieldRecordType);
-      bool _Special = false;
-      ParseRecordRecursive(Ctx, FieldRecordType, _Special, Visited);
-      if (_Special)
-        Special = true;
-    }
-  }
-  uint64_t GuestSize = GetRecordSize(type, guest_triple);
-  uint64_t HostSize = GetRecordSize(type, host_triple);
-
-  auto Record = &records[type];
-  if (GuestSize != HostSize) {
-    Special = 1;
-  }
-  if (type->isUnionType()) {
-    Record->is_union = true;
-  }
-  if (!Record->decl) {
-    Record->type = type;
-    Record->decl = RecordDecl;
-    if (RecordDecl->getIdentifier())
-      Record->type_name = RecordDecl->getIdentifier()->getName().str();
-  }
-  Record->guest_size = GuestSize;
-  Record->host_size = HostSize;
-  if (Record->type_name.empty()) {
-    Record->is_special = false;
-  } else
-    Record->is_special = Special;
-}
-
-// Type to String
-std::string WrapperGenerator::TypeStringify(const Type *Type,
-                                         FieldDecl *FieldDecl,
-                                         ParmVarDecl *ParmDecl,
-                                         std::string& PreDecl,
-                                         std::string indent,
-                                         std::string Name) {
-  std::string res;
-  std::string name = FieldDecl
-                         ? FieldDecl->getNameAsString()
-                         : (ParmDecl ? ParmDecl->getNameAsString() : Name);
-  if (Type->isPointerType()) {
-    auto PointeeType = Type->getPointeeType();
-    if (PointeeType->isBuiltinType()) {
-      res +=
-          StripTypedef(PointeeType)->getCanonicalTypeInternal().getAsString();
-    } else if (PointeeType->isRecordType()) {
-      if (records.find(StripTypedef(PointeeType)) != records.end() &&
-          records[StripTypedef(PointeeType)].is_special) {
-        res += (PointeeType->isUnionType() ? "union " : "struct ") + records[StripTypedef(PointeeType)].type_name;
-      } else {
-        res += "void";
-      }
-    } else {
-      res += "void";
-    }
-    res += " *" + name;
-  } else if (Type->isEnumeralType()) {
-    res += "int " + name;
-  } else if (Type->isRecordType()) {
-    if (records.find(StripTypedef(Type->getCanonicalTypeInternal())) !=
-            records.end() &&
-        records[StripTypedef(Type->getCanonicalTypeInternal())].is_special) {
-      res += Type->isUnionType() ? "union " : "struct ";
-      res += records[StripTypedef(Type->getCanonicalTypeInternal())].type_name;
-      res += " ";
-    } else {
-      res += AnonRecordDecl(Type->getAs<RecordType>(), PreDecl, indent + "  ");
-    }
-    res += name;
-  } else if (Type->isConstantArrayType()) {
-    auto ArrayType =
-        dyn_cast<ConstantArrayType>(Type->getAsArrayTypeUnsafe());
-    int EleSize = ArrayType->getSize().getZExtValue();
-    if (ArrayType->getElementType()->isPointerType()) {
-      res += "void *";
-    } else if (ArrayType->getElementType()->isEnumeralType()) {
-      res += "int ";
-    } else if (ArrayType->getElementType()->isRecordType()) {
-      auto RecordType = ArrayType->getElementType()->getAs<clang::RecordType>();
-      auto RecordDecl = RecordType->getDecl();
-      if (RecordDecl->isCompleteDefinition()) {
-        auto& Ctx = RecordDecl->getDeclContext()->getParentASTContext();
-        PreDecl += "#include \"";
-        PreDecl += GetDeclHeaderFile(Ctx, RecordDecl);
-        PreDecl += "\"";
-        PreDecl += "\n";
-      }
-      res += StripTypedef(ArrayType->getElementType())
-                ->getCanonicalTypeInternal()
-                .getAsString();
-    } else {
-      res += StripTypedef(ArrayType->getElementType())
-                 ->getCanonicalTypeInternal()
-                 .getAsString();
-    }
-    res += " ";
-    res += name;
-    res += "[";
-    res += std::to_string(EleSize);
-    res += "]";
-  } else {
-    res += StripTypedef(Type->getCanonicalTypeInternal())
-               ->getCanonicalTypeInternal()
-               .getAsString();
-    res += " ";
-    res += name;
-  }
-  return indent + res;
-}
-
-// Type to String, less detail
-std::string WrapperGenerator::SimpleTypeStringify(const Type *Type,
-                                               FieldDecl *FieldDecl,
-                                               ParmVarDecl *ParmDecl,
-                                               std::string indent,
-                                               std::string Name) {
-  std::string res;
-  std::string name = FieldDecl
-                         ? FieldDecl->getNameAsString()
-                         : (ParmDecl ? ParmDecl->getNameAsString() : Name);
-  if (Type->isPointerType()) {
-    res += "void *" + name;
-  } else if (Type->isEnumeralType()) {
-    res += "int ";
-    res += name;
-  } else if (Type->isRecordType()) {
-    if (records.find(StripTypedef(Type->getCanonicalTypeInternal())) !=
-        records.end()) {
-      res += Type->isUnionType() ? "union " : "struct ";
-      res += records[StripTypedef(Type->getCanonicalTypeInternal())].type_name;
-      res += " ";
-    } else {
-      res += SimpleAnonRecordDecl(Type->getAs<RecordType>(), indent);
-    }
-    res += name;
-  } else if (Type->isConstantArrayType()) {
-    auto ArrayType =
-        dyn_cast<ConstantArrayType>(Type->getAsArrayTypeUnsafe());
-    int EleSize = ArrayType->getSize().getZExtValue();
-    if (ArrayType->getElementType()->isPointerType()) {
-      res += "void *";
-    } else {
-      res += StripTypedef(ArrayType->getElementType())
-                 ->getCanonicalTypeInternal()
-                 .getAsString();
-    }
-    res += " ";
-    res += name;
-    res += "[";
-    res += std::to_string(EleSize);
-    res += "]";
-  } else {
-    res += StripTypedef(Type->getCanonicalTypeInternal())
-               ->getCanonicalTypeInternal()
-               .getAsString();
-    res += " ";
-    res += name;
-  }
-  return indent + res;
-}
-
-std::string WrapperGenerator::AnonRecordDecl(const RecordType *Type, std::string& PreDecl, std::string indent) {
-  auto RecordDecl = Type->getDecl();
-  std::string res;
-  res += Type->isUnionType() ? "union {\n" : "struct {\n";
-  for (const auto &field : RecordDecl->fields()) {
-    auto FieldType = field->getType();
-    res += TypeStringify(StripTypedef(FieldType), field, nullptr, PreDecl, indent + "  ");
-    res += ";\n";
-  }
-  res += indent + "} ";
-  return res;
-}
-
-std::string
-WrapperGenerator::SimpleAnonRecordDecl(const RecordType *Type, std::string indent) {
-  auto RecordDecl = Type->getDecl();
-  std::string res;
-  res += Type->isUnionType() ? "union {\n" : "struct {\n";
-  for (const auto &field : RecordDecl->fields()) {
-    auto FieldType = field->getType();
-    res += SimpleTypeStringify(StripTypedef(FieldType), field, nullptr, indent + "  ");
-    res += ";\n";
-  }
-  res += indent + "} ";
-  return res;
-}
-
-// Get func info from FunctionType
-FuncDefinition WrapperGenerator::GetFuncDefinition(const Type *Type) {
-  FuncDefinition res;
-  std::string PreDecl;
-  auto ProtoType = Type->getAs<FunctionProtoType>();
-  res.ret = StripTypedef(ProtoType->getReturnType());
-  res.ret_str =
-      TypeStringify(StripTypedef(ProtoType->getReturnType()), nullptr, nullptr, PreDecl);
-  for (unsigned i = 0; i < ProtoType->getNumParams(); i++) {
-    auto ParamType = ProtoType->getParamType(i);
-    res.arg_types.push_back(StripTypedef(ParamType));
-    res.arg_types_str.push_back(
-        TypeStringify(StripTypedef(ParamType), nullptr, nullptr, PreDecl));
-    res.arg_names.push_back(std::string("a") + std::to_string(i));
-  }
-  if (ProtoType->isVariadic()) {
-    res.is_variadaic = true;
-  }
-  res.arg_size = ProtoType->getNumParams();
-  return res;
-}
-
-// Get funcdecl info from FunctionDecl
-FuncDefinition WrapperGenerator::GetFuncDefinition(FunctionDecl *Decl) {
-  FuncDefinition res;
-  std::string PreDecl;
-  auto RetType = Decl->getReturnType();
-  res.ret = RetType.getTypePtr();
-  res.ret_str = TypeStringify(StripTypedef(RetType), nullptr, nullptr, PreDecl);
-  for (unsigned i = 0; i < Decl->getNumParams(); i++) {
-    auto ParamDecl = Decl->getParamDecl(i);
-    auto ParamType = ParamDecl->getType();
-    res.arg_types.push_back(ParamType.getTypePtr());
-    res.arg_types_str.push_back(
-        TypeStringify(StripTypedef(ParamType), nullptr, nullptr, PreDecl));
-    res.arg_names.push_back(ParamDecl->getNameAsString());
-  }
-  if (Decl->isVariadic()) {
-    res.is_variadaic = true;
-  }
-  return res;
-}
-
-// Get the offset diff between two different triple
-std::vector<uint64_t> WrapperGenerator::GetRecordFieldOffDiff(
-    const Type *Type, const std::string &GuestTriple,
-    const std::string &HostTriple, std::vector<uint64_t> &GuestFieldOff,
-    std::vector<uint64_t> &HostFieldOff) {
-  std::string PreDecl;
-  std::string Code = TypeStringify(Type, nullptr, nullptr, PreDecl, "", "dummy;");
-  std::vector<uint64_t> OffsetDiff;
-  GuestFieldOff = GetRecordFieldOff(Code, GuestTriple);
-  HostFieldOff = GetRecordFieldOff(Code, HostTriple);
-  if (GuestFieldOff.size() != HostFieldOff.size()) {
-    // Should not happen
-    std::cout << "Greater field offsets in guest than in host" << std::endl;
-    return OffsetDiff;
-  }
-  for (size_t i = 0; i < GuestFieldOff.size(); i++) {
-    OffsetDiff.push_back(GuestFieldOff[i] - HostFieldOff[i]);
-  }
-  return OffsetDiff;
-}
-
-// Get the size under a specific triple
-uint64_t WrapperGenerator::GetRecordSize(const Type *Type,
-                                    const std::string &Triple) {
-  std::string PreDecl;
-  std::string Code = TypeStringify(Type, nullptr, nullptr, PreDecl, "", "dummy;");
-  auto Size = ::GetRecordSize(PreDecl + Code, Triple);
-  return Size;
-}
-
-// Get the align under a specific triple
-CharUnits::QuantityType WrapperGenerator::GetRecordAlign(const Type *Type,
-                                     const std::string &Triple) {
-  std::string PreDecl{};
-  std::string Code = TypeStringify(Type, nullptr, nullptr, PreDecl, "", "dummy;");
-  return ::GetRecordAlign(PreDecl + Code, Triple);
-}
-
-// Generate the func sig by type, used for export func
-std::string WrapperGenerator::GetFuncSig(ASTContext *CTX,
-                                         const FuncInfo &Func) {
-  std::string sig;
-  auto Decl = Func.decl;
-  auto Type = Decl->getType().getTypePtr();
-  auto ProtoType = Type->getAs<FunctionProtoType>();
-  auto RetType = ProtoType->getReturnType();
-
-  sig += TypeToSig(CTX, RetType.getTypePtr());
-  sig += "F";
-  if (Func.has_special_arg || Func.has_special_ret || Func.has_callback_arg) {
-    sig += "E";
-  }
-  if (ProtoType->getNumParams()) {
-    for (unsigned i = 0; i < ProtoType->getNumParams(); i++) {
-      sig += TypeToSig(CTX, ProtoType->getParamType(i).getTypePtr());
-    }
-  } else {
-    sig += "v";
-  }
-  if (Decl->isVariadic()) {
-    sig += "VV";
-  }
-  return sig;
-}
-
-// Generate the func sig by type, used for callbacks
-std::string WrapperGenerator::GetFuncSig(ASTContext *CTX,
-                                         const Type *Type) {
-  std::string sig;
-  auto ProtoType = Type->getAs<FunctionProtoType>();
-  auto RetType = ProtoType->getReturnType();
-  sig += TypeToSig(CTX, RetType.getTypePtr());
-  sig += "F";
-  if (ProtoType->getNumParams()) {
-    for (unsigned i = 0; i < ProtoType->getNumParams(); i++) {
-      sig += TypeToSig(CTX, ProtoType->getParamType(i).getTypePtr());
-    }
-  } else {
-    sig += "v";
-  }
-  return sig;
-}
diff --git a/wrapperhelper/gen.h b/wrapperhelper/gen.h
deleted file mode 100644
index 55ecd202..00000000
--- a/wrapperhelper/gen.h
+++ /dev/null
@@ -1,144 +0,0 @@
-#pragma once
-#include <clang/AST/ASTContext.h>
-#include <clang/AST/Type.h>
-#include <cstdint>
-#include <string>
-#include <vector>
-#include <set>
-
-struct FuncDefinition {
-    std::vector<const clang::Type*> arg_types;
-    std::vector<std::string> arg_types_str;
-    std::vector<std::string> arg_names;
-    const clang::Type* ret;
-    std::string ret_str;
-    int arg_size;
-    bool is_variadaic;
-};
-
-struct FuncInfo {
-    const clang::Type* type;
-    clang::FunctionDecl* decl;
-    std::string func_name;
-    bool is_weak;
-    bool is_variadaic;
-    bool has_special_arg;
-    bool has_special_ret;
-    bool has_callback_arg;
-    std::vector<const clang::Type*> callback_args;
-};
-
-struct RecordInfo {
-    const clang::Type* type;
-    clang::RecordDecl* decl;
-    std::string type_name;
-    bool is_union;
-    bool is_special;
-
-    uint64_t guest_size;
-    uint64_t host_size;
-    std::vector<const clang::Type*> callback_fields;
-};
-
-struct ObjectInfo {
-    const clang::Type* type;
-    std::string object_name;
-};
-
-struct WrapperGenerator {
-
-    void Init(const std::string& libname, const std::string& host_triple, const std::string& guest_triple) {
-        this->host_triple = host_triple;
-        this->guest_triple = guest_triple;
-        this->libname = libname;
-        this->my_lib_type = libname + "_my_t";
-        this->my_lib = "my_lib";
-    }
-
-    void Prepare(clang::ASTContext* Ctx);
-
-    std::string GenCallbackTypeDefs(clang::ASTContext* Ctx);
-
-    std::string GenFuncDeclare(clang::ASTContext* Ctx) {
-        std::string res{};
-        for (const auto& func : funcs) {
-            res += GenDeclare(Ctx, func.second);
-        }
-        return res;
-    }
-    std::string GenRecordDeclare(clang::ASTContext* Ctx) {
-        std::string res{};
-        for (const auto& st : records) {
-            if (st.second.host_size == st.second.guest_size)
-                res += GenDeclare(Ctx, st.second);
-            else {
-                res += GenDeclareDiffTriple(Ctx, st.second, guest_triple, host_triple);
-            }
-        }
-        return res;
-    }
-
-    std::string GenFuncDefine(clang::ASTContext* Ctx) {
-        std::string res{};
-        for (const auto& func : funcs) {
-            res += GenDefine(Ctx, func.second);
-        }
-        return res;
-    }
-
-    std::string GenCallbackWrap(clang::ASTContext* Ctx) {
-        std::string res{};
-        for (const auto& func : funcs) {
-            res += GenCallbackWrap(Ctx, func.second);
-        }
-        for (const auto& st : records) {
-            res += GenCallbackWrap(Ctx, st.second);
-        }
-        return res;
-    }
-    
-    std::string GenRecordConvert(clang::ASTContext* Ctx) {
-        (void)Ctx;
-        std::string res;
-        for (const auto& record : records) {
-            if (record.second.host_size != record.second.guest_size) {
-                res += GenRecordConvert(record.second);
-            }
-        }
-        return res;
-    }
-
-    std::map<const clang::Type*, FuncInfo> funcs;
-    std::map<const clang::Type*, RecordInfo> records;
-    std::map<const clang::Type*, ObjectInfo> objects;
-
-    std::map<const clang::Type*, std::string> callbacks;
-
-    std::string host_triple;
-    std::string guest_triple;
-    std::string libname;
-    std::string my_lib_type;
-    std::string my_lib;
-private:
-    std::string GenRecordConvert(const RecordInfo& Record);
-    std::string GenDeclare(clang::ASTContext* Ctx, const FuncInfo& Func);
-    std::string GenDefine(clang::ASTContext* Ctx, const FuncInfo& Func);
-    std::string GenCallbackWrap(clang::ASTContext* Ctx, const FuncInfo& Func);
-    std::string GenDeclareDiffTriple(clang::ASTContext* Ctx, const RecordInfo& Record, const std::string& GuestTriple, const std::string& HostTriple);
-    std::string GenDeclare(clang::ASTContext* Ctx, const RecordInfo& Struct);
-    std::string GenCallbackWrap(clang::ASTContext* Ctx, const RecordInfo& Struct);
-
-    void ParseRecordRecursive(clang::ASTContext* Ctx, const clang::Type* Type, bool& Special, std::set<const clang::Type*>& Visited);
-    std::string TypeStringify(const clang::Type* Type, clang::FieldDecl* FieldDecl, clang::ParmVarDecl* ParmDecl, std::string& PreDecl, std::string indent = "", std::string Name = "");
-    std::string SimpleTypeStringify(const clang::Type* Type, clang::FieldDecl* FieldDecl, clang::ParmVarDecl* ParmDecl, std::string indent = "", std::string Name = "");
-    std::string AnonRecordDecl(const clang::RecordType* Type, std::string& PreDecl, std::string indent);
-    std::string SimpleAnonRecordDecl(const clang::RecordType* Type, std::string indent);
-    FuncDefinition GetFuncDefinition(const clang::Type* Type);
-    FuncDefinition GetFuncDefinition(clang::FunctionDecl* Decl);
-    uint64_t GetRecordSize(const clang::Type* Type, const std::string& Triple);
-    std::vector<uint64_t> GetRecordFieldOffDiff(const clang::Type* Type, const std::string& GuestTriple, const std::string& HostTriple, std::vector<uint64_t>& GuestFieldOff, std::vector<uint64_t>& HostFieldOff);
-    clang::CharUnits::QuantityType GetRecordAlign(const clang::Type* Type, const std::string& Triple);
-
-    std::string GetFuncSig(clang::ASTContext* CTX, const FuncInfo& Decl);
-    std::string GetFuncSig(clang::ASTContext* CTX, const clang::Type* Type);
-};
diff --git a/wrapperhelper/include-fixed/bits/stdint-intn.h b/wrapperhelper/include-fixed/bits/stdint-intn.h
new file mode 100644
index 00000000..14abdb46
--- /dev/null
+++ b/wrapperhelper/include-fixed/bits/stdint-intn.h
@@ -0,0 +1,32 @@
+/* Define intN_t types.
+   Copyright (C) 2017-2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.
+
+This file has been adapted to work with the 'wrapperhelper' project on the 09/06/2024.
+*/
+
+#ifndef _BITS_STDINT_INTN_H
+#define _BITS_STDINT_INTN_H	1
+
+#include <bits/types.h>
+
+typedef __int8_t int8_t;
+typedef __int16_t int16_t;
+typedef __int32_t int32_t;
+typedef __int64_t int64_t;
+
+#endif /* bits/stdint-intn.h */
diff --git a/wrapperhelper/include-fixed/bits/stdint-least.h b/wrapperhelper/include-fixed/bits/stdint-least.h
new file mode 100644
index 00000000..d9753d7d
--- /dev/null
+++ b/wrapperhelper/include-fixed/bits/stdint-least.h
@@ -0,0 +1,39 @@
+/* Define int_leastN_t and uint_leastN types.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.
+
+This file has been adapted to work with the 'wrapperhelper' project on the 09/06/2024.
+*/
+
+#ifndef _BITS_STDINT_LEAST_H
+#define _BITS_STDINT_LEAST_H	1
+
+#include <bits/types.h>
+
+/* Signed.  */
+typedef __int_least8_t int_least8_t;
+typedef __int_least16_t int_least16_t;
+typedef __int_least32_t int_least32_t;
+typedef __int_least64_t int_least64_t;
+
+/* Unsigned.  */
+typedef __uint_least8_t uint_least8_t;
+typedef __uint_least16_t uint_least16_t;
+typedef __uint_least32_t uint_least32_t;
+typedef __uint_least64_t uint_least64_t;
+
+#endif /* bits/stdint-least.h */
diff --git a/wrapperhelper/include-fixed/bits/stdint-uintn.h b/wrapperhelper/include-fixed/bits/stdint-uintn.h
new file mode 100644
index 00000000..cf2e8b4e
--- /dev/null
+++ b/wrapperhelper/include-fixed/bits/stdint-uintn.h
@@ -0,0 +1,32 @@
+/* Define uintN_t types.
+   Copyright (C) 2017-2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.
+
+This file has been adapted to work with the 'wrapperhelper' project on the 09/06/2024.
+*/
+
+#ifndef _BITS_STDINT_UINTN_H
+#define _BITS_STDINT_UINTN_H	1
+
+#include <bits/types.h>
+
+typedef __uint8_t uint8_t;
+typedef __uint16_t uint16_t;
+typedef __uint32_t uint32_t;
+typedef __uint64_t uint64_t;
+
+#endif /* bits/stdint-uintn.h */
diff --git a/wrapperhelper/include-fixed/bits/types.h b/wrapperhelper/include-fixed/bits/types.h
new file mode 100644
index 00000000..7d1eddda
--- /dev/null
+++ b/wrapperhelper/include-fixed/bits/types.h
@@ -0,0 +1,192 @@
+/* bits/types.h -- definitions of __*_t types underlying *_t types.
+   Copyright (C) 2002-2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.
+
+This file has been adapted to work with the 'wrapperhelper' project on the 09/06/2024.
+*/
+
+/*
+ * Never include this file directly; use <sys/types.h> instead.
+ */
+
+#ifndef	_BITS_TYPES_H
+#define	_BITS_TYPES_H	1
+
+#include <features.h>
+#include <bits/wordsize.h>
+#include <bits/timesize.h>
+
+/* Convenience types.  */
+typedef unsigned char __u_char;
+typedef unsigned short int __u_short;
+typedef unsigned int __u_int;
+typedef unsigned long int __u_long;
+
+/* Fixed-size types, underlying types depend on word size and compiler.  */
+#pragma wrappers allow_ints_ext
+
+/* Smallest types with at least a given width.  */
+typedef __int8_t __int_least8_t;
+typedef __uint8_t __uint_least8_t;
+typedef __int16_t __int_least16_t;
+typedef __uint16_t __uint_least16_t;
+typedef __int32_t __int_least32_t;
+typedef __uint32_t __uint_least32_t;
+typedef __int64_t __int_least64_t;
+typedef __uint64_t __uint_least64_t;
+
+/* quad_t is also 64 bits.  */
+typedef __int64_t __quad_t;
+typedef __uint64_t __u_quad_t;
+
+/* Largest integral types.  */
+typedef __int64_t __intmax_t;
+typedef __uint64_t __uintmax_t;
+
+
+/* The machine-dependent file <bits/typesizes.h> defines __*_T_TYPE
+   macros for each of the OS types we define below.  The definitions
+   of those macros must use the following macros for underlying types.
+   We define __S<SIZE>_TYPE and __U<SIZE>_TYPE for the signed and unsigned
+   variants of each of the following integer types on this machine.
+
+	16		-- "natural" 16-bit type (always short)
+	32		-- "natural" 32-bit type (always int)
+	64		-- "natural" 64-bit type (long or long long)
+	LONG32	-- 32-bit type, traditionally long
+	QUAD		-- 64-bit type, traditionally long long
+	WORD		-- natural type of __WORDSIZE bits (int or long)
+	LONGWORD	-- type of __WORDSIZE bits, traditionally long
+
+   We distinguish WORD/LONGWORD, 32/LONG32, and 64/QUAD so that the
+   conventional uses of `long' or `long long' type modifiers match the
+   types we define, even when a less-adorned type would be the same size.
+   This matters for (somewhat) portably writing printf/scanf formats for
+   these types, where using the appropriate l or ll format modifiers can
+   make the typedefs and the formats match up across all GNU platforms.  If
+   we used `long' when it's 64 bits where `long long' is expected, then the
+   compiler would warn about the formats not matching the argument types,
+   and the programmer changing them to shut up the compiler would break the
+   program's portability.
+
+   Here we assume what is presently the case in all the GCC configurations
+   we support: long long is always 64 bits, long is always word/address size,
+   and int is always 32 bits.  */
+
+#define	__S16_TYPE		__int16_t
+#define __U16_TYPE		__uint16_t
+#define	__S32_TYPE		__int32_t
+#define __U32_TYPE		__uint32_t
+#define __SLONGWORD_TYPE	long int
+#define __ULONGWORD_TYPE	unsigned long int
+# define __SQUAD_TYPE		__int64_t
+# define __UQUAD_TYPE		__uint64_t
+# define __SWORD_TYPE		long int
+# define __UWORD_TYPE		unsigned long int
+# define __SLONG32_TYPE		__int32_t
+# define __ULONG32_TYPE		__uint32_t
+# define __S64_TYPE		__int64_t
+# define __U64_TYPE		__uint64_t
+# define __STD_TYPE		typedef
+#include <bits/typesizes.h>	/* Defines __*_T_TYPE macros.  */
+#include <bits/time64.h>	/* Defines __TIME*_T_TYPE macros.  */
+
+
+__STD_TYPE __DEV_T_TYPE __dev_t;	/* Type of device numbers.  */
+__STD_TYPE __UID_T_TYPE __uid_t;	/* Type of user identifications.  */
+__STD_TYPE __GID_T_TYPE __gid_t;	/* Type of group identifications.  */
+__STD_TYPE __INO_T_TYPE __ino_t;	/* Type of file serial numbers.  */
+__STD_TYPE __INO64_T_TYPE __ino64_t;	/* Type of file serial numbers (LFS).*/
+__STD_TYPE __MODE_T_TYPE __mode_t;	/* Type of file attribute bitmasks.  */
+__STD_TYPE __NLINK_T_TYPE __nlink_t;	/* Type of file link counts.  */
+__STD_TYPE __OFF_T_TYPE __off_t;	/* Type of file sizes and offsets.  */
+__STD_TYPE __OFF64_T_TYPE __off64_t;	/* Type of file sizes and offsets (LFS).  */
+__STD_TYPE __PID_T_TYPE __pid_t;	/* Type of process identifications.  */
+__STD_TYPE __FSID_T_TYPE __fsid_t;	/* Type of file system IDs.  */
+__STD_TYPE __CLOCK_T_TYPE __clock_t;	/* Type of CPU usage counts.  */
+__STD_TYPE __RLIM_T_TYPE __rlim_t;	/* Type for resource measurement.  */
+__STD_TYPE __RLIM64_T_TYPE __rlim64_t;	/* Type for resource measurement (LFS).  */
+__STD_TYPE __ID_T_TYPE __id_t;		/* General type for IDs.  */
+__STD_TYPE __TIME_T_TYPE __time_t;	/* Seconds since the Epoch.  */
+__STD_TYPE __USECONDS_T_TYPE __useconds_t; /* Count of microseconds.  */
+__STD_TYPE __SUSECONDS_T_TYPE __suseconds_t; /* Signed count of microseconds.  */
+__STD_TYPE __SUSECONDS64_T_TYPE __suseconds64_t;
+
+__STD_TYPE __DADDR_T_TYPE __daddr_t;	/* The type of a disk address.  */
+__STD_TYPE __KEY_T_TYPE __key_t;	/* Type of an IPC key.  */
+
+/* Clock ID used in clock and timer functions.  */
+__STD_TYPE __CLOCKID_T_TYPE __clockid_t;
+
+/* Timer ID returned by `timer_create'.  */
+__STD_TYPE __TIMER_T_TYPE __timer_t;
+
+/* Type to represent block size.  */
+__STD_TYPE __BLKSIZE_T_TYPE __blksize_t;
+
+/* Types from the Large File Support interface.  */
+
+/* Type to count number of disk blocks.  */
+__STD_TYPE __BLKCNT_T_TYPE __blkcnt_t;
+__STD_TYPE __BLKCNT64_T_TYPE __blkcnt64_t;
+
+/* Type to count file system blocks.  */
+__STD_TYPE __FSBLKCNT_T_TYPE __fsblkcnt_t;
+__STD_TYPE __FSBLKCNT64_T_TYPE __fsblkcnt64_t;
+
+/* Type to count file system nodes.  */
+__STD_TYPE __FSFILCNT_T_TYPE __fsfilcnt_t;
+__STD_TYPE __FSFILCNT64_T_TYPE __fsfilcnt64_t;
+
+/* Type of miscellaneous file system fields.  */
+__STD_TYPE __FSWORD_T_TYPE __fsword_t;
+
+__STD_TYPE __SSIZE_T_TYPE __ssize_t; /* Type of a byte count, or error.  */
+
+/* Signed long type used in system calls.  */
+__STD_TYPE __SYSCALL_SLONG_TYPE __syscall_slong_t;
+/* Unsigned long type used in system calls.  */
+__STD_TYPE __SYSCALL_ULONG_TYPE __syscall_ulong_t;
+
+/* These few don't really vary by system, they always correspond
+   to one of the other defined types.  */
+typedef __off64_t __loff_t;	/* Type of file sizes and offsets (LFS).  */
+typedef char *__caddr_t;
+
+/* Duplicates info from stdint.h but this is used in unistd.h.  */
+__STD_TYPE __SWORD_TYPE __intptr_t;
+
+/* Duplicate info from sys/socket.h.  */
+__STD_TYPE __U32_TYPE __socklen_t;
+
+/* C99: An integer type that can be accessed as an atomic entity,
+   even in the presence of asynchronous interrupts.
+   It is not currently necessary for this to be machine-specific.  */
+typedef int __sig_atomic_t;
+
+/* Seconds since the Epoch, visible to user code when time_t is too
+   narrow only for consistency with the old way of widening too-narrow
+   types.  User code should never use __time64_t.  */
+#if __TIMESIZE == 64 && defined __LIBC
+# define __time64_t __time_t
+#elif __TIMESIZE != 64
+__STD_TYPE __TIME64_T_TYPE __time64_t;
+#endif
+
+#undef __STD_TYPE
+
+#endif /* bits/types.h */
diff --git a/wrapperhelper/include-fixed/stdint.h b/wrapperhelper/include-fixed/stdint.h
new file mode 100644
index 00000000..a3341ad2
--- /dev/null
+++ b/wrapperhelper/include-fixed/stdint.h
@@ -0,0 +1,276 @@
+/* Copyright (C) 1997-2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.
+
+This file has been adapted to work with the 'wrapperhelper' project on the 09/06/2024.
+*/
+
+/*
+ *	ISO C99: 7.18 Integer types <stdint.h>
+ */
+
+#ifndef _STDINT_H
+#define _STDINT_H	1
+
+#define __GLIBC_INTERNAL_STARTING_HEADER_IMPLEMENTATION
+#include <bits/libc-header-start.h>
+#include <bits/types.h>
+#include <bits/wchar.h>
+#include <bits/wordsize.h>
+
+#pragma wrappers allow_ints_ext
+
+/* Exact integral types.  */
+
+/* Signed.  */
+#include <bits/stdint-intn.h>
+
+/* Unsigned.  */
+#include <bits/stdint-uintn.h>
+
+
+/* Small types.  */
+#include <bits/stdint-least.h>
+
+
+/* Fast types.  */
+
+/* Signed.  */
+typedef int8_t int_fast8_t;
+typedef int16_t int_fast16_t;
+typedef int32_t int_fast32_t;
+typedef int64_t int_fast64_t;
+
+/* Unsigned.  */
+typedef uint8_t uint_fast8_t;
+typedef uint16_t uint_fast16_t;
+typedef uint32_t uint_fast32_t;
+typedef uint64_t uint_fast64_t;
+
+
+/* Types for `void *' pointers.  */
+# ifndef __intptr_t_defined
+typedef signed long intptr_t;
+#  define __intptr_t_defined
+# endif
+typedef unsigned long uintptr_t;
+
+
+/* Largest integral types.  */
+typedef int64_t		intmax_t;
+typedef uint64_t		uintmax_t;
+
+
+# if __WORDSIZE == 64
+#  define __INT64_C(c)	c ## L
+#  define __UINT64_C(c)	c ## UL
+# else
+#  define __INT64_C(c)	c ## LL
+#  define __UINT64_C(c)	c ## ULL
+# endif
+
+/* Limits of integral types.  */
+
+/* Minimum of signed integral types.  */
+# define INT8_MIN		(-128)
+# define INT16_MIN		(-32767-1)
+# define INT32_MIN		(-2147483647-1)
+# define INT64_MIN		(-__INT64_C(9223372036854775807)-1)
+/* Maximum of signed integral types.  */
+# define INT8_MAX		(127)
+# define INT16_MAX		(32767)
+# define INT32_MAX		(2147483647)
+# define INT64_MAX		(__INT64_C(9223372036854775807))
+
+/* Maximum of unsigned integral types.  */
+# define UINT8_MAX		(255)
+# define UINT16_MAX		(65535)
+# define UINT32_MAX		(4294967295U)
+# define UINT64_MAX		(__UINT64_C(18446744073709551615))
+
+
+/* Minimum of signed integral types having a minimum size.  */
+# define INT_LEAST8_MIN		(-128)
+# define INT_LEAST16_MIN	(-32767-1)
+# define INT_LEAST32_MIN	(-2147483647-1)
+# define INT_LEAST64_MIN	(-__INT64_C(9223372036854775807)-1)
+/* Maximum of signed integral types having a minimum size.  */
+# define INT_LEAST8_MAX		(127)
+# define INT_LEAST16_MAX	(32767)
+# define INT_LEAST32_MAX	(2147483647)
+# define INT_LEAST64_MAX	(__INT64_C(9223372036854775807))
+
+/* Maximum of unsigned integral types having a minimum size.  */
+# define UINT_LEAST8_MAX	(255)
+# define UINT_LEAST16_MAX	(65535)
+# define UINT_LEAST32_MAX	(4294967295U)
+# define UINT_LEAST64_MAX	(__UINT64_C(18446744073709551615))
+
+
+/* Minimum of fast signed integral types having a minimum size.  */
+# define INT_FAST8_MIN		(-128)
+# define INT_FAST16_MIN    INT16_MIN
+# define INT_FAST32_MIN    INT32_MIN
+# define INT_FAST64_MIN		(-__INT64_C(9223372036854775807)-1)
+/* Maximum of fast signed integral types having a minimum size.  */
+# define INT_FAST8_MAX		(127)
+# define INT_FAST16_MAX    INT16_MAX
+# define INT_FAST32_MAX    INT32_MAX
+# define INT_FAST64_MAX		(__INT64_C(9223372036854775807))
+
+/* Maximum of fast unsigned integral types having a minimum size.  */
+# define UINT_FAST8_MAX		(255)
+# define UINT_FAST16_MAX   UINT16_MAX
+# define UINT_FAST32_MAX   UINT32_MAX
+# define UINT_FAST64_MAX	(__UINT64_C(18446744073709551615))
+
+
+/* Values to test for integral types holding `void *' pointer.  */
+# if __WORDSIZE == 64
+#  define INTPTR_MIN		(-9223372036854775807L-1)
+#  define INTPTR_MAX		(9223372036854775807L)
+#  define UINTPTR_MAX		(18446744073709551615UL)
+# else
+#  define INTPTR_MIN		(-2147483647-1)
+#  define INTPTR_MAX		(2147483647)
+#  define UINTPTR_MAX		(4294967295U)
+# endif
+
+
+/* Minimum for largest signed integral type.  */
+# define INTMAX_MIN		(-__INT64_C(9223372036854775807)-1)
+/* Maximum for largest signed integral type.  */
+# define INTMAX_MAX		(__INT64_C(9223372036854775807))
+
+/* Maximum for largest unsigned integral type.  */
+# define UINTMAX_MAX		(__UINT64_C(18446744073709551615))
+
+
+/* Limits of other integer types.  */
+
+/* Limits of `ptrdiff_t' type.  */
+# if __WORDSIZE == 64
+#  define PTRDIFF_MIN		(-9223372036854775807L-1)
+#  define PTRDIFF_MAX		(9223372036854775807L)
+# else
+#  if __WORDSIZE32_PTRDIFF_LONG
+#   define PTRDIFF_MIN		(-2147483647L-1)
+#   define PTRDIFF_MAX		(2147483647L)
+#  else
+#   define PTRDIFF_MIN		(-2147483647-1)
+#   define PTRDIFF_MAX		(2147483647)
+#  endif
+# endif
+
+/* Limits of `sig_atomic_t'.  */
+# define SIG_ATOMIC_MIN		(-2147483647-1)
+# define SIG_ATOMIC_MAX		(2147483647)
+
+/* Limit of `size_t' type.  */
+# if __WORDSIZE == 64
+#  define SIZE_MAX		(18446744073709551615UL)
+# else
+#  if __WORDSIZE32_SIZE_ULONG
+#   define SIZE_MAX		(4294967295UL)
+#  else
+#   define SIZE_MAX		(4294967295U)
+#  endif
+# endif
+
+/* Limits of `wchar_t'.  */
+# ifndef WCHAR_MIN
+/* These constants might also be defined in <wchar.h>.  */
+#  define WCHAR_MIN		__WCHAR_MIN
+#  define WCHAR_MAX		__WCHAR_MAX
+# endif
+
+/* Limits of `wint_t'.  */
+# define WINT_MIN		(0u)
+# define WINT_MAX		(4294967295u)
+
+/* Signed.  */
+# define INT8_C(c)	c
+# define INT16_C(c)	c
+# define INT32_C(c)	c
+# if __WORDSIZE == 64
+#  define INT64_C(c)	c ## L
+# else
+#  define INT64_C(c)	c ## LL
+# endif
+
+/* Unsigned.  */
+# define UINT8_C(c)	c
+# define UINT16_C(c)	c
+# define UINT32_C(c)	c ## U
+# if __WORDSIZE == 64
+#  define UINT64_C(c)	c ## UL
+# else
+#  define UINT64_C(c)	c ## ULL
+# endif
+
+/* Maximal type.  */
+# if __WORDSIZE == 64
+#  define INTMAX_C(c)	c ## L
+#  define UINTMAX_C(c)	c ## UL
+# else
+#  define INTMAX_C(c)	c ## LL
+#  define UINTMAX_C(c)	c ## ULL
+# endif
+
+#if __GLIBC_USE (IEC_60559_BFP_EXT_C23)
+
+# define INT8_WIDTH 8
+# define UINT8_WIDTH 8
+# define INT16_WIDTH 16
+# define UINT16_WIDTH 16
+# define INT32_WIDTH 32
+# define UINT32_WIDTH 32
+# define INT64_WIDTH 64
+# define UINT64_WIDTH 64
+
+# define INT_LEAST8_WIDTH 8
+# define UINT_LEAST8_WIDTH 8
+# define INT_LEAST16_WIDTH 16
+# define UINT_LEAST16_WIDTH 16
+# define INT_LEAST32_WIDTH 32
+# define UINT_LEAST32_WIDTH 32
+# define INT_LEAST64_WIDTH 64
+# define UINT_LEAST64_WIDTH 64
+
+# define INT_FAST8_WIDTH 8
+# define UINT_FAST8_WIDTH 8
+# define INT_FAST16_WIDTH __WORDSIZE
+# define UINT_FAST16_WIDTH __WORDSIZE
+# define INT_FAST32_WIDTH __WORDSIZE
+# define UINT_FAST32_WIDTH __WORDSIZE
+# define INT_FAST64_WIDTH 64
+# define UINT_FAST64_WIDTH 64
+
+# define INTPTR_WIDTH __WORDSIZE
+# define UINTPTR_WIDTH __WORDSIZE
+
+# define INTMAX_WIDTH 64
+# define UINTMAX_WIDTH 64
+
+# define PTRDIFF_WIDTH __WORDSIZE
+# define SIG_ATOMIC_WIDTH 32
+# define SIZE_WIDTH __WORDSIZE
+# define WCHAR_WIDTH 32
+# define WINT_WIDTH 32
+
+#endif
+
+#endif /* stdint.h */
diff --git a/wrapperhelper/main.cpp b/wrapperhelper/main.cpp
deleted file mode 100644
index b5a18a16..00000000
--- a/wrapperhelper/main.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#include "ast.h"
-#include "utils.h"
-
-static void dump_usage() {
-    std::string Usage = R"usage(
-        usage: command <filename> <libname> [guest_triple] [host_triple] -- <clang_flags>
-                <filename>    : set the header file to be parsed
-                <libname>     : set libname required for wrapping func
-                [guest_triple]: set guest triple: can be arm32/arm64/x86/x64, default is x64
-                [host_triple] : set host triple: can be arm32/arm64/x86/x64, default is arm64
-                --            : mandatory
-                <clang_flags> : extra compiler flags
-    )usage";
-    std::cerr << Usage << std::endl;
-}
-
-std::string parse_triple(const char* arg) {
-    if (strcmp(arg, "x86") == 0) {
-        return "i386-pc-linux-gnu";
-    } else if (strcmp(arg, "x64") == 0) {
-        return "x86_64-pc-linux-gnu";
-    } else if (strcmp(arg, "arm32") == 0) {
-        return "armv7-unknown-linux-gnueabihf";
-    } else if (strcmp(arg, "arm64") == 0) {
-        return "aarch64-unknown-linux-gnu";
-    } else {
-        std::cerr << "Invalid triple: '" << arg << "'\n";
-        dump_usage();
-        return "";
-    }
-}
-
-int main(int argc, const char* argv[]) {
-    if (argc < 4) {
-        dump_usage();
-        return 0;
-    }
-    std::string libname = argv[2];
-    std::string guest_triple = parse_triple("x64");
-    std::string host_triple = parse_triple("arm64");
-    if (argc >= 5) {
-        guest_triple = parse_triple(argv[3]);
-    }
-    if (argc >= 6) {
-        host_triple = parse_triple(argv[4]);
-    }
-    bool has_necessary_tag = false;
-    for (int i = 0; i < argc; i++) {
-        if (strcmp(argv[i], "--") == 0) {
-            has_necessary_tag = true;
-            break;
-        }
-    }
-    if (!has_necessary_tag) {
-        std::cerr << "Please add '--' after the triples" << std::endl;
-        dump_usage();
-        return 0;
-    }
-    std::string err;
-    auto compile_db = clang::tooling::FixedCompilationDatabase::loadFromCommandLine(argc, argv, err);
-    clang::tooling::ClangTool Tool(*compile_db, {argv[1]});
-    Tool.appendArgumentsAdjuster([&guest_triple](const clang::tooling::CommandLineArguments &args, clang::StringRef) {
-        clang::tooling::CommandLineArguments adjusted_args = args;
-        adjusted_args.push_back(std::string{"-target"});
-        adjusted_args.push_back(guest_triple);
-        return adjusted_args;
-    });
-    return Tool.run(std::make_unique<MyFrontendActionFactory>(libname, host_triple, guest_triple).get());
-}
diff --git a/wrapperhelper/src/cstring.c b/wrapperhelper/src/cstring.c
new file mode 100644
index 00000000..b38a43e6
--- /dev/null
+++ b/wrapperhelper/src/cstring.c
@@ -0,0 +1,115 @@
+#include "cstring.h"
+
+#include <string.h>
+
+#define STRING_MIN_CAP 8
+
+string_t *string_new(void) {
+	string_t *ret = malloc(sizeof(*ret));
+	if (!ret) return NULL;
+	char *buf = malloc(sizeof(char));
+	if (!buf) {
+		free(ret);
+		return NULL;
+	}
+	buf[0] = '\0';
+	ret->ssize = ret->scap = 0; ret->buf = buf;
+	return ret;
+}
+
+string_t *string_new_cap(size_t cap) {
+	string_t *ret = malloc(sizeof(*ret));
+	if (!ret) return NULL;
+	cap = (cap < STRING_MIN_CAP) ? STRING_MIN_CAP : cap;
+	ret->buf = malloc((cap + 1) * sizeof(char));
+	if (!ret->buf) {
+		free(ret);
+		return NULL;
+	}
+	ret->buf[0] = '\0';
+	ret->scap = cap;
+	ret->ssize = 0;
+	return ret;
+}
+
+int string_reserve(string_t *s, size_t cap) {
+	size_t new_cap = (cap < STRING_MIN_CAP) ? STRING_MIN_CAP : cap;
+	if (new_cap <= s->scap) return 1;
+	
+	void *new_buf = realloc(s->buf, sizeof(char) * (new_cap + 1));
+	if (!new_buf) return 0;
+	s->buf = new_buf;
+	s->scap = new_cap;
+	return 1;
+}
+
+void string_del(string_t *s) {
+	if (s->buf) free(s->buf);
+	free(s);
+}
+
+char *string_steal(string_t *s) {
+	char *ret = s->buf;
+	free(s);
+	return ret;
+}
+
+int string_add_char(string_t *s, char elem) {
+	if (s->ssize >= s->scap) {
+		size_t new_cap = (s->scap < STRING_MIN_CAP) ? STRING_MIN_CAP : s->scap * 2;
+		char *new_buf = realloc(s->buf, sizeof(char) * (new_cap + 1));
+		if (!new_buf) return 0;
+		s->buf = new_buf;
+		s->scap = new_cap;
+	}
+	s->buf[s->ssize++] = elem;
+	s->buf[s->ssize] = '\0';
+	return 1;
+}
+
+int string_add_string(string_t *s1, string_t *s2) {
+	if (s1->ssize + s2->ssize > s1->scap) {
+		size_t new_cap = (s1->scap < STRING_MIN_CAP) ? STRING_MIN_CAP : s1->scap * 2;
+		while (s1->ssize + s2->ssize > new_cap) {
+			new_cap = new_cap * 2;
+		}
+		char *new_buf = realloc(s1->buf, sizeof(char) * (new_cap + 1));
+		if (!new_buf) return 0;
+		s1->buf = new_buf;
+		s1->scap = new_cap;
+	}
+	memcpy(s1->buf + s1->ssize, s2->buf, s2->ssize);
+	s1->ssize += s2->ssize;
+	s1->buf[s1->ssize] = '\0';
+	return 1;
+}
+
+void string_pop(string_t *s) {
+	if (!s->ssize) return;
+	s->buf[--s->ssize] = '\0';
+	if (s->ssize < s->scap / 4) {
+		size_t new_cap = (s->scap / 2 < STRING_MIN_CAP) ? STRING_MIN_CAP : s->scap / 2;
+		if (new_cap == s->scap) return;
+		void *new_buf = realloc(s->buf, sizeof(char) * (new_cap + 1));
+		if (!new_buf) return; // We don't really care if the realloc fails, we just need to not update anything
+		s->buf = new_buf;
+		s->scap = new_cap;
+	}
+}
+
+string_t *string_dup(string_t const *s) {
+	string_t *ret = string_new_cap(s->ssize);
+	if (!ret) return NULL;
+	memcpy(ret->buf, s->buf, s->ssize + 1);
+	ret->ssize = s->ssize;
+	return ret;
+}
+
+string_t *string_concat(string_t const *l, string_t const *r) {
+	string_t *ret = string_new_cap(l->ssize + r->ssize);
+	if (!ret) return NULL;
+	memcpy(ret->buf, l->buf, l->ssize);
+	memcpy(ret->buf + l->ssize, r->buf, r->ssize);
+	ret->buf[ret->ssize] = '\0';
+	return ret;
+}
diff --git a/wrapperhelper/src/cstring.h b/wrapperhelper/src/cstring.h
new file mode 100644
index 00000000..14032bd6
--- /dev/null
+++ b/wrapperhelper/src/cstring.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#ifndef STRING_H
+#define STRING_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/** Thread-unsafe string implementation, with support for raw NULL bytes
+ * USAGE:
+ * ======
+ * string_t ------------ The string type.
+ * string_new ---------- Creates a new string.
+ * string_new_cap ------ Creates a new string with a given capacity. Takes the capacity.
+ * string_reserve ------ Ensures a string has at least a given capacity. Takes the string and the capacity.
+ * string_del ---------- Frees a string. Takes the string.
+ * string_steal -------- Frees a string, keeping the content alive. Takes the string. The content (also returned) needs to be freed separately.
+ * string_add_char ----- Add a character at the end. Takes the string and the new character. May increase the string capacity.
+ * string_add_string --- Add a string at the end in-place. Takes both strings. May increase the string capacity.
+ * string_pop ---------- Pops the last character. Takes the string. May reduce the string capacity.
+ * string_dup ---------- Duplicate a string. Takes the string. Does not free the old string.
+ * string_concat ------- Concatenate two strings. Takes both strings. Does not free any string.
+ * string_len ---------- String length. Takes the string.
+ * string_cap ---------- String capacity. Takes the string.
+ * string_content ------ Pointer to the string content. Valid C string. Takes the string.
+ * string_begin -------- Start of the string. Takes the string.
+ * string_end ---------- End of the string. Points to unmanaged memory. Takes the string.
+ * string_last --------- Last element of the string. Points to invalid memory if size is zero. Takes the string.
+ * string_for ---------- Iterate over the characters of a string. This is a for loop. Takes the iterator name and the string.
+ * 
+ * EXAMPLE:
+ * ========
+ * Source main.c:
+ * -------------------
+// ...
+int main() {
+	string_t *str = string_new_cap(2);
+	if (!str) {
+		printf("Error: failed to allocate new string\n");
+		return 2;
+	}
+	string_add_char(str, 'H'); // Cannot fail
+	string_add_char(str, 'i'); // Cannot fail
+	if (!string_add_char(str, '!')) {
+		printf("Error: failed to add char to string\n");
+		return 2;
+	}
+	printf("String length: %zu: \"%s\"\n", string_len(str), string_content(str)); // 3, "Hi!"
+	string_del(str);
+}
+ */
+
+typedef struct string_s {
+	size_t ssize, scap;
+	char *buf;
+} string_t;
+
+string_t *string_new(void);
+string_t *string_new_cap(size_t cap);
+int       string_reserve(string_t *s, size_t cap);
+void      string_del(string_t *s);
+char     *string_steal(string_t *s);
+int       string_add_char(string_t *s, char elem);
+int       string_add_string(string_t *s1, string_t *s2);
+void      string_pop(string_t *s);
+string_t *string_dup(string_t const *s);
+string_t *string_concat(string_t const *l, string_t const *r);
+#define string_len(s) ((s)->ssize)
+#define string_cap(s) ((s)->scap)
+#define string_content(s) ((s)->buf)
+#define string_begin(s) ((s)->buf)
+#define string_end(s) ((s)->buf + (s)->ssize)
+#define string_last(s) ((s)->buf[(s)->ssize - 1])
+#define string_for(itname, s) \
+	for (char *itname = string_begin((s)); itname != string_end((s)); ++itname)
+
+#endif // STRING_H
diff --git a/wrapperhelper/src/generator.c b/wrapperhelper/src/generator.c
new file mode 100644
index 00000000..3df744cb
--- /dev/null
+++ b/wrapperhelper/src/generator.c
@@ -0,0 +1,922 @@
+#include "generator.h"
+
+#include "lang.h"
+#include "prepare.h"
+
+static const char *rft2str[8] = {
+	[RQT_FUN] = "",
+	[RQT_FUN_2] = "",
+	[RQT_FUN_MY] = "(my) ",
+	[RQT_FUN_D] = "(D) ",
+	[RQT_DATA] = "",
+	[RQT_DATAV] = "(V) ",
+	[RQT_DATAB] = "(B) ",
+	[RQT_DATAM] = "(my) ",
+};
+#define IS_RQT_FUN2(rty) (((rty) == RQT_FUN_2) || ((rty) == RQT_FUN_D))
+#define IS_RQT_FUNCTION(rty) ((rty) < RQT_DATA)
+
+void request_print(request_t *req) {
+	printf("%s", string_content(req->obj_name));
+	if (req->has_default && req->has_val && (IS_RQT_FUNCTION(req->def.rty) != IS_RQT_FUNCTION(req->val.rty))) {
+		printf(" => conflict: was/is data, is/was function\n");
+	}
+	int is_fun;
+	if (req->has_default) is_fun = IS_RQT_FUNCTION(req->def.rty);
+	else if (req->has_val) is_fun = IS_RQT_FUNCTION(req->val.rty);
+	else {
+		printf(" => (no value)\n");
+		return;
+	}
+	
+	if (is_fun) {
+		printf(" => %sfunction", req->weak ? "weak " : "");
+		if (req->has_default) {
+			printf(" with %sdefault %s%s%s%s",
+				req->default_comment ? "commented " : "",
+				rft2str[req->def.rty],
+				string_content(req->def.fun.typ),
+				(req->def.rty == RQT_FUN_2) ? " -> " : "",
+				(req->def.rty == RQT_FUN_2) ? string_content(req->def.fun.fun2) : "");
+		}
+		if (req->has_val) {
+			printf(" with solved %s%s%s%s",
+				rft2str[req->val.rty],
+				string_content(req->val.fun.typ),
+				(req->val.rty == RQT_FUN_2) ? " -> " : "",
+				(req->val.rty == RQT_FUN_2) ? string_content(req->val.fun.fun2) : "");
+		}
+	} else {
+		printf(" => %sdata", req->weak ? "weak " : "");
+		if (req->has_default) {
+			if (req->def.dat.has_size) printf(" with default %zu", req->def.dat.sz);
+			else printf(" with no default");
+		}
+		if (req->has_val) {
+			if (req->def.dat.has_size) printf(" with solved %zu", req->def.dat.sz);
+			else printf(" with no solved");
+		}
+	}
+	printf("\n");
+}
+void request_print_check(request_t *req) {
+	if (req->has_default && req->has_val && (IS_RQT_FUNCTION(req->def.rty) != IS_RQT_FUNCTION(req->val.rty))) {
+		printf("%s => conflict: was/is data, is/was function\n", string_content(req->obj_name));
+	}
+	int is_fun;
+	if (req->has_default) is_fun = IS_RQT_FUNCTION(req->def.rty);
+	else if (req->has_val) is_fun = IS_RQT_FUNCTION(req->val.rty);
+	else {
+		printf(" => (no value)\n");
+		return;
+	}
+	if (is_fun) {
+		if (req->has_val) {
+			if (req->has_default) {
+				int similar = 1;
+				// if (req->def.rty != req->val.rty) similar = 0;
+				if (similar && strcmp(string_content(req->def.fun.typ), string_content(req->val.fun.typ))) {
+					// similar = 0;
+					// TODO
+					if (req->def.rty == RQT_FUN_MY) {
+						similar =
+							string_len(req->def.fun.typ) >= 3 &&
+							string_content(req->def.fun.typ)[2] == 'E' &&
+							!strncmp(string_content(req->def.fun.typ), string_content(req->val.fun.typ), 2) &&
+							!strcmp(string_content(req->def.fun.typ) + 3, string_content(req->val.fun.typ) + 2);
+						if (similar) {
+							// We need to add the 'E' back here
+						}
+					} else {
+						similar = 0;
+					}
+				}
+				if (!similar) {
+					printf("%s => %sfunction with %sdefault %s%s%s%s and different solved %s%s%s%s\n",
+						string_content(req->obj_name),
+						req->weak ? "weak " : "",
+						req->default_comment ? "commented " : "",
+						rft2str[req->def.rty],
+						string_content(req->def.fun.typ),
+						(req->def.rty == RQT_FUN_2) ? " -> " : "",
+						(req->def.rty == RQT_FUN_2) ? string_content(req->def.fun.fun2) : "",
+						rft2str[req->val.rty],
+						string_content(req->val.fun.typ),
+						(req->val.rty == RQT_FUN_2) ? " -> " : "",
+						(req->val.rty == RQT_FUN_2) ? string_content(req->val.fun.fun2) : "");
+				}
+			} else {
+				printf("%s => %sfunction with solved %s%s%s%s\n",
+					string_content(req->obj_name),
+					req->weak ? "weak " : "",
+					rft2str[req->val.rty],
+					string_content(req->val.fun.typ),
+					(req->val.rty == RQT_FUN_2) ? " -> " : "",
+					(req->val.rty == RQT_FUN_2) ? string_content(req->val.fun.fun2) : "");
+			}
+		} else if (req->has_default) {
+			/* printf("%s => unsolved %sfunction with %sdefault %s%s%s%s\n",
+				string_content(req->obj_name),
+				req->weak ? "weak " : "",
+				req->default_comment ? "commented " : "",
+				rft2str[req->def.rty],
+				string_content(req->def.fun.typ),
+				(req->def.rty == RQT_FUN_2) ? " -> " : "",
+				(req->def.rty == RQT_FUN_2) ? string_content(req->def.fun.fun2) : ""); */
+		}
+	} else {
+		printf("%s => %sdata", string_content(req->obj_name), req->weak ? "weak " : "");
+		if (req->has_default) {
+			if (req->def.dat.has_size) printf(" with default %zu", req->def.dat.sz);
+			else printf(" with no default");
+		}
+		if (req->has_val) {
+			if (req->def.dat.has_size) printf(" with solved %zu", req->def.dat.sz);
+			else printf(" with no solved");
+		}
+	}
+}
+void request_del(request_t *req) {
+	string_del(req->obj_name);
+	if (req->has_default) {
+		switch (req->def.rty) {
+		case RQT_FUN:    string_del(req->def.fun.typ);                                break;
+		case RQT_FUN_2:  string_del(req->def.fun.typ); string_del(req->def.fun.fun2); break;
+		case RQT_FUN_MY: string_del(req->def.fun.typ);                                break;
+		case RQT_FUN_D:  string_del(req->def.fun.typ); string_del(req->def.fun.fun2); break;
+		case RQT_DATA:   break;
+		case RQT_DATAV:  break;
+		case RQT_DATAB:  break;
+		case RQT_DATAM:  break;
+		}
+	}
+	if (req->has_val) {
+		switch (req->val.rty) {
+		case RQT_FUN:    string_del(req->val.fun.typ);                                break;
+		case RQT_FUN_2:  string_del(req->val.fun.typ); string_del(req->val.fun.fun2); break;
+		case RQT_FUN_MY: string_del(req->val.fun.typ);                                break;
+		case RQT_FUN_D:  string_del(req->val.fun.typ); string_del(req->val.fun.fun2); break;
+		case RQT_DATA:   break;
+		case RQT_DATAV:  break;
+		case RQT_DATAB:  break;
+		case RQT_DATAM:  break;
+		}
+	}
+}
+
+static int valid_reqtype(string_t *t) {
+	const char *s = string_content(t);
+	if (!((s[0] >= 'A') && (s[0] <= 'Z')) && !((s[0] >= 'a') && (s[0] <= 'z'))) return 0;
+	if (s[1] != 'F') return 0;
+	for (size_t i = 2; i < string_len(t); ++i) {
+		if (!((s[i] >= 'A') && (s[i] <= 'Z')) && !((s[i] >= 'a') && (s[i] <= 'z'))) return 0;
+	}
+	return 1;
+}
+static const char *rqt_suffix[8] = {
+	[RQT_FUN] = "",
+	[RQT_FUN_2] = "2",
+	[RQT_FUN_MY] = "M",
+	[RQT_FUN_D] = "D",
+	[RQT_DATA] = "",
+	[RQT_DATAV] = "V",
+	[RQT_DATAB] = "B",
+	[RQT_DATAM] = "M",
+};
+
+static void request_output(FILE *f, request_t *req) {
+	if (!req->has_val) {
+		if (!req->has_default) {
+			// printf("Warning: %s has no value and no default, assuming function\n", string_content(req->obj_name));
+			fprintf(f, "//GO%s(%s, \n", req->weak ? "W" : "", string_content(req->obj_name));
+		} else if (IS_RQT_FUNCTION(req->def.rty)) {
+			fprintf(f, "%sGO%s%s(%s, %s%s%s%s%s)%s\n",
+				req->default_comment ? "//" : "",
+				req->weak ? "W" : "",
+				rqt_suffix[req->def.rty],
+				string_content(req->obj_name),
+				valid_reqtype(req->def.fun.typ) ? "" : "\"",
+				string_content(req->def.fun.typ),
+				valid_reqtype(req->def.fun.typ) ? "" : "\"",
+				IS_RQT_FUN2(req->def.rty) ? ", " : "",
+				IS_RQT_FUN2(req->def.rty) ? string_content(req->def.fun.fun2) : "",
+				req->default_comment ? "" : " // Warning: failed to confirm");
+		} else {
+			if (req->def.dat.has_size) {
+				fprintf(f, "%sDATA%s%s(%s, %zu) // Warning: failed to confirm\n",
+					req->default_comment ? "//" : "",
+					req->weak ? "W" : "",
+					rqt_suffix[req->def.rty],
+					string_content(req->obj_name),
+					req->def.dat.sz);
+			} else {
+				fprintf(f, "//DATA%s%s(%s, \n",
+					req->weak ? "W" : "",
+					rqt_suffix[req->def.rty],
+					string_content(req->obj_name));
+			}
+		}
+	} else {
+		if (IS_RQT_FUNCTION(req->val.rty)) {
+			int is_comment =
+				(req->has_default && !req->default_comment) ? (req->val.rty != req->def.rty) : (req->val.rty != RQT_FUN);
+			fprintf(f, "%sGO%s%s(%s, %s%s%s)\n",
+				is_comment ? "//" : "",
+				req->weak ? "W" : "",
+				rqt_suffix[req->val.rty],
+				string_content(req->obj_name),
+				string_content(req->val.fun.typ),
+				IS_RQT_FUN2(req->val.rty) ? ", " : "",
+				IS_RQT_FUN2(req->val.rty) ? req->val.fun.fun2 ? string_content(req->val.fun.fun2) : "<error: no val>" : "");
+		} else {
+			if (req->val.dat.has_size) {
+				int is_comment = !req->has_default || req->default_comment || (req->def.rty != req->val.rty);
+				fprintf(f, "%sDATA%s(%s, %zu)\n",
+					is_comment ? "//" : "",
+					rqt_suffix[req->val.rty],
+					string_content(req->obj_name),
+					req->val.dat.sz);
+			} else {
+				fprintf(f, "//DATA%s(%s, \n",
+					rqt_suffix[req->val.rty],
+					string_content(req->obj_name));
+			}
+		}
+	}
+}
+void output_from_requests(FILE *f, VECTOR(requests) *reqs) {
+	fprintf(f, "#if !(defined(GO) && defined(GOM) && defined(GO2) && defined(DATA))\n#error Meh...\n#endif\n\n");
+	vector_for(requests, req, reqs) {
+		request_output(f, req);
+	}
+}
+
+VECTOR_IMPL(requests, request_del)
+
+VECTOR(requests) *requests_from_file(const char *filename, FILE *f) {
+	prepare_t *prep = prepare_new_file(f, filename);
+	if (!prep) {
+		printf("Failed to create the prepare structure for the requests file\n");
+		return NULL;
+	}
+	
+	VECTOR(requests) *ret = vector_new(requests);
+	if (!ret) {
+		prepare_del(prep);
+		return NULL;
+	}
+	
+	int lineno = 1;
+	
+	// Ignore the first 3 lines
+	preproc_token_t tok;
+	do {
+		tok = pre_next_token(prep, 0);
+		if (tok.tokt == PPTOK_NEWLINE) ++lineno;
+	} while (!preproc_token_isend(&tok) && (lineno < 4));
+	
+	// TODO: better conditionals handling
+	// Also, for now assume we have no definition
+	int if_depth = 0, entered_depth = 0;
+	while (1) {
+		int is_comment = 0;
+		tok = pre_next_token(prep, 1);
+		while (tok.tokt == PPTOK_START_LINE_COMMENT) {
+			is_comment = 1;
+			// Empty destructor
+			tok = pre_next_token(prep, 1);
+		}
+		if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_HASH)) {
+			if (is_comment) {
+				preproc_token_del(&tok);
+				tok = pre_next_newline_token(prep); // Returns a newline
+				++lineno;
+				continue;
+			}
+			tok = pre_next_token(prep, 0);
+			if (tok.tokt != PPTOK_IDENT) {
+				printf("Error: invalid requests file: invalid preprocessor line\n");
+				preproc_token_del(&tok);
+				goto failed;
+			}
+			if (!strcmp(string_content(tok.tokv.str), "ifdef")) {
+				string_del(tok.tokv.str);
+				tok = pre_next_token(prep, 0);
+				if (tok.tokt != PPTOK_IDENT) {
+					printf("Error: invalid requests file: invalid '#ifdef' line\n");
+					preproc_token_del(&tok);
+					goto failed;
+				}
+				++if_depth;
+				string_del(tok.tokv.str);
+				tok = pre_next_token(prep, 0);
+			} else if (!strcmp(string_content(tok.tokv.str), "ifndef")) {
+				string_del(tok.tokv.str);
+				tok = pre_next_token(prep, 0);
+				if (tok.tokt != PPTOK_IDENT) {
+					printf("Error: invalid requests file: invalid '#ifndef' line\n");
+					preproc_token_del(&tok);
+					goto failed;
+				}
+				if (if_depth == entered_depth) ++entered_depth;
+				++if_depth;
+				string_del(tok.tokv.str);
+				tok = pre_next_token(prep, 0);
+			} else if (!strcmp(string_content(tok.tokv.str), "else")) {
+				string_del(tok.tokv.str);
+				tok = pre_next_token(prep, 0);
+				if (if_depth == entered_depth + 1) ++entered_depth;
+				else if (if_depth == entered_depth) --entered_depth;
+			} else if (!strcmp(string_content(tok.tokv.str), "endif")) {
+				string_del(tok.tokv.str);
+				tok = pre_next_token(prep, 0);
+				if (if_depth == entered_depth) --entered_depth;
+				--if_depth;
+			} else {
+				printf("Error: invalid requests file: invalid preprocessor command '%s'\n", string_content(tok.tokv.str));
+				string_del(tok.tokv.str);
+				goto failed;
+			}
+			while (!preproc_token_isend(&tok) && (tok.tokt != PPTOK_NEWLINE)) {
+				preproc_token_del(&tok);
+				tok = pre_next_token(prep, 0);
+			}
+			++lineno;
+			if (preproc_token_isend(&tok)) {
+				if (tok.tokt == PPTOK_EOF) goto success;
+				else {
+					preproc_token_del(&tok);
+					goto failed;
+				}
+			}
+		} else if (tok.tokt == PPTOK_NEWLINE) {
+			++lineno;
+		} else if (tok.tokt == PPTOK_EOF) {
+			goto success;
+		} else if ((tok.tokt == PPTOK_IDENT)
+		        && (!strcmp(string_content(tok.tokv.str), "GO")
+		         || !strcmp(string_content(tok.tokv.str), "GO2")
+		         || !strcmp(string_content(tok.tokv.str), "GOD")
+		         || !strcmp(string_content(tok.tokv.str), "GOM")
+		         || !strcmp(string_content(tok.tokv.str), "GOW")
+		         || !strcmp(string_content(tok.tokv.str), "GOW2")
+		         || !strcmp(string_content(tok.tokv.str), "GOWD")
+		         || !strcmp(string_content(tok.tokv.str), "GOWM"))) {
+			int isweak = (string_content(tok.tokv.str)[2] == 'W');
+			request_t req = {
+				.has_default = 0,
+				.default_comment = is_comment,
+				.has_val = 0,
+				.obj_name = NULL,
+				.weak = isweak,
+				.def = {
+					.rty =
+						(string_content(tok.tokv.str)[isweak ? 3 : 2] == '2') ? RQT_FUN_2 :
+						(string_content(tok.tokv.str)[isweak ? 3 : 2] == 'D') ? RQT_FUN_D :
+						(string_content(tok.tokv.str)[isweak ? 3 : 2] == 'M') ? RQT_FUN_MY : RQT_FUN,
+					.fun.typ = NULL,
+					.fun.fun2 = NULL,
+				},
+			};
+			string_del(tok.tokv.str);
+			tok = pre_next_token(prep, 0);
+			if ((tok.tokt != PPTOK_SYM) || (tok.tokv.sym != SYM_LPAREN)) {
+				printf("Error: invalid requests file: invalid GO line %d (lparen)\n", lineno);
+				preproc_token_del(&tok);
+				goto failed;
+			}
+			// Empty destructor
+			tok = pre_next_token(prep, 0);
+			if (tok.tokt != PPTOK_IDENT) {
+				printf("Error: invalid requests file: invalid GO line %d (obj_name)\n", lineno);
+				preproc_token_del(&tok);
+				goto failed;
+			}
+			req.obj_name = tok.tokv.str;
+			// Token moved
+			tok = pre_next_token(prep, 0);
+			if ((tok.tokt != PPTOK_SYM) || (tok.tokv.sym != SYM_COMMA)) {
+				printf("Error: invalid requests file: invalid GO line %d (comma)\n", lineno);
+				string_del(req.obj_name);
+				preproc_token_del(&tok);
+				goto failed;
+			}
+			// Empty destructor
+			tok = pre_next_token(prep, 0);
+			if ((tok.tokt == PPTOK_IDENT) || (tok.tokt == PPTOK_STRING)) {
+				req.def.fun.typ = (tok.tokt == PPTOK_STRING) ? tok.tokv.sstr : tok.tokv.str;
+				req.has_default = 1;
+				// Token moved
+				tok = pre_next_token(prep, 0);
+				if ((req.def.rty == RQT_FUN_2) || (req.def.rty == RQT_FUN_D)) {
+					if ((tok.tokt != PPTOK_SYM) || (tok.tokv.sym != SYM_COMMA)) {
+						printf("Error: invalid requests file: invalid GO line %d (comma 2)\n", lineno);
+						string_del(req.obj_name);
+						string_del(req.def.fun.typ);
+						preproc_token_del(&tok);
+						goto failed;
+					}
+					// Empty destructor
+					tok = pre_next_token(prep, 0);
+					if (tok.tokt != PPTOK_IDENT) {
+						printf("Error: invalid requests file: invalid GO line %d (redirect)\n", lineno);
+						string_del(req.obj_name);
+						string_del(req.def.fun.typ);
+						preproc_token_del(&tok);
+						goto failed;
+					}
+					req.def.fun.fun2 = tok.tokv.str;
+					// Token moved
+					tok = pre_next_token(prep, 0);
+				}
+				if ((tok.tokt != PPTOK_SYM) || (tok.tokv.sym != SYM_RPAREN)) {
+					printf("Error: invalid requests file: invalid GO line %d (rparen)\n", lineno);
+					string_del(req.obj_name);
+					string_del(req.def.fun.typ);
+					if (req.def.fun.fun2) string_del(req.def.fun.fun2);
+					preproc_token_del(&tok);
+					goto failed;
+				}
+				// Empty destructor
+				tok = pre_next_token(prep, 0);
+			}
+			if (tok.tokt != PPTOK_NEWLINE) {
+				printf("Error: invalid requests file: invalid GO line %d (newline)\n", lineno);
+				string_del(req.obj_name);
+				if (req.def.fun.typ) string_del(req.def.fun.typ);
+				if (req.def.fun.fun2) string_del(req.def.fun.fun2);
+				preproc_token_del(&tok);
+				goto failed;
+			}
+			if (if_depth == entered_depth) {
+				if (!vector_push(requests, ret, req)) {
+					printf("Error: failed to add request for %s\n", string_content(req.obj_name));
+					string_del(req.obj_name);
+					if (req.def.fun.typ) string_del(req.def.fun.typ);
+					if (req.def.fun.fun2) string_del(req.def.fun.fun2);
+					// Empty destructor
+					goto failed;
+				}
+			}
+			++lineno;
+		} else if ((tok.tokt == PPTOK_IDENT)
+		        && (!strcmp(string_content(tok.tokv.str), "DATA")
+		         || !strcmp(string_content(tok.tokv.str), "DATAV")
+		         || !strcmp(string_content(tok.tokv.str), "DATAB")
+		         || !strcmp(string_content(tok.tokv.str), "DATAM"))) {
+			request_t req = {
+				.has_default = 1,
+				.default_comment = is_comment,
+				.has_val = 0,
+				.obj_name = NULL,
+				.weak = (string_content(tok.tokv.str)[4] == 'V'),
+				.def = {
+					.rty =
+						(string_content(tok.tokv.str)[4] == 'V') ? RQT_DATAV :
+						(string_content(tok.tokv.str)[4] == 'B') ? RQT_DATAB :
+						(string_content(tok.tokv.str)[4] == 'M') ? RQT_DATAM : RQT_DATA,
+					.dat.has_size = 0,
+					.dat.sz = 0,
+				},
+			};
+			string_del(tok.tokv.str);
+			tok = pre_next_token(prep, 0);
+			if ((tok.tokt != PPTOK_SYM) || (tok.tokv.sym != SYM_LPAREN)) {
+				printf("Error: invalid requests file: invalid DATA line %d (lparen)\n", lineno);
+				preproc_token_del(&tok);
+				goto failed;
+			}
+			// Empty destructor
+			tok = pre_next_token(prep, 0);
+			if (tok.tokt != PPTOK_IDENT) {
+				printf("Error: invalid requests file: invalid DATA line %d (obj_name)\n", lineno);
+				preproc_token_del(&tok);
+				goto failed;
+			}
+			req.obj_name = tok.tokv.str;
+			// Token moved
+			tok = pre_next_token(prep, 0);
+			if ((tok.tokt != PPTOK_SYM) || (tok.tokv.sym != SYM_COMMA)) {
+				printf("Error: invalid requests file: invalid DATA line %d (comma)\n", lineno);
+				string_del(req.obj_name);
+				preproc_token_del(&tok);
+				goto failed;
+			}
+			// Empty destructor
+			tok = pre_next_token(prep, 0);
+			if (tok.tokt == PPTOK_NUM) {
+				num_constant_t cst;
+				if (!num_constant_convert(tok.tokv.str, &cst)) {
+					printf("Error: invalid requests file: invalid DATA line %d (num conversion)\n", lineno);
+					string_del(req.obj_name);
+					preproc_token_del(&tok);
+					goto failed;
+				}
+				switch (cst.typ) {
+				case NCT_FLOAT:
+				case NCT_DOUBLE:
+				case NCT_LDOUBLE:
+					printf("Error: invalid requests file: invalid DATA line %d (num conversion)\n", lineno);
+					string_del(req.obj_name);
+					preproc_token_del(&tok);
+					goto failed;
+				case NCT_INT32:  req.def.dat.sz = (size_t)cst.val.i32; break;
+				case NCT_UINT32: req.def.dat.sz = (size_t)cst.val.u32; break;
+				case NCT_INT64:  req.def.dat.sz = (size_t)cst.val.i64; break;
+				case NCT_UINT64: req.def.dat.sz = (size_t)cst.val.u64; break;
+				}
+				req.def.dat.has_size = 1;
+				// Token moved
+				tok = pre_next_token(prep, 0);
+				if ((tok.tokt != PPTOK_SYM) || (tok.tokv.sym != SYM_RPAREN)) {
+					printf("Error: invalid requests file: invalid DATA line %d (rparen)\n", lineno);
+					string_del(req.obj_name);
+					preproc_token_del(&tok);
+					goto failed;
+				}
+				// Empty destructor
+				tok = pre_next_token(prep, 0);
+			}
+			if (tok.tokt != PPTOK_NEWLINE) {
+				printf("Error: invalid requests file: invalid DATA line %d (newline)\n", lineno);
+				string_del(req.obj_name);
+				preproc_token_del(&tok);
+				goto failed;
+			}
+			if (if_depth == entered_depth) {
+				if (!vector_push(requests, ret, req)) {
+					printf("Error: failed to add request for %s\n", string_content(req.obj_name));
+					string_del(req.obj_name);
+					// Empty destructor
+					goto failed;
+				}
+			}
+			++lineno;
+		} else {
+			if (is_comment) {
+				preproc_token_del(&tok);
+				tok = pre_next_newline_token(prep); // Returns a newline
+				++lineno;
+				continue;
+			}
+			printf("Error: invalid requests file: invalid token:\n");
+			preproc_token_print(&tok);
+			preproc_token_del(&tok);
+			goto failed;
+		}
+	}
+	
+failed:
+	prepare_del(prep);
+	vector_del(requests, ret);
+	return NULL;
+	
+success:
+	prepare_del(prep);
+	return ret;
+}
+
+static int is_simple_type_ptr_to(type_t *typ, int *needs_D, int *needs_my) {
+	switch (typ->typ) {
+	case TYPE_BUILTIN:
+		return 1; // Assume pointers to builtin are simple
+	case TYPE_ARRAY:
+		if (typ->val.array.array_sz == (size_t)-1) return 0; // VLA are not simple
+		return is_simple_type_ptr_to(typ->val.array.typ, needs_D, needs_my);
+	case TYPE_STRUCT_UNION:
+		if (typ->val.st->explicit_simple) return 1;
+		if (typ->_internal_use) return 1; // Recursive structures are OK as long as every other members are OK
+		if (!typ->val.st->is_defined) return 1; // Undefined structures are OK since they are opaque
+		typ->_internal_use = 1;
+		for (size_t i = 0; i < typ->val.st->nmembers; ++i) {
+			st_member_t *mem = &typ->val.st->members[i];
+			if (!is_simple_type_ptr_to(mem->typ, needs_D, needs_my)) {
+				typ->_internal_use = 0;
+				return 0;
+			}
+		}
+		typ->_internal_use = 0;
+		return 1;
+	case TYPE_ENUM:
+		return is_simple_type_ptr_to(typ->val.typ, needs_D, needs_my);
+	case TYPE_PTR:
+		return is_simple_type_ptr_to(typ->val.typ, needs_D, needs_my);
+	case TYPE_FUNCTION:
+		*needs_my = 1;
+		return 1;
+	default:
+		printf("Error: is_simple_type_ptr_to on unknown type %u\n", typ->typ);
+		return 0;
+	}
+}
+static int is_simple_type(type_t *typ, int *needs_D, int *needs_my) {
+	switch (typ->typ) {
+	case TYPE_BUILTIN:
+		return 1; // Assume pointers to builtin are simple
+	case TYPE_ARRAY:
+		if (typ->val.array.array_sz == (size_t)-1) return 0; // VLA are not simple
+		return is_simple_type_ptr_to(typ->val.array.typ, needs_D, needs_my);
+	case TYPE_STRUCT_UNION:
+		if (typ->val.st->explicit_simple) return 1;
+		if (typ->_internal_use) return 1; // Recursive structures are OK as long as every other members are OK
+		// if (!typ->val.st->is_defined) return 1; // Undefined structures are OK since they are opaque
+		// To be safe, don't allow opaque structures
+		if (!typ->val.st->is_defined) return 0;
+		typ->_internal_use = 1;
+		for (size_t i = 0; i < typ->val.st->nmembers; ++i) {
+			st_member_t *mem = &typ->val.st->members[i];
+			if (!is_simple_type(mem->typ, needs_D, needs_my)) {
+				typ->_internal_use = 0;
+				return 0;
+			}
+		}
+		typ->_internal_use = 0;
+		return 1;
+	case TYPE_ENUM:
+		return is_simple_type(typ->val.typ, needs_D, needs_my);
+	case TYPE_PTR:
+		return is_simple_type_ptr_to(typ->val.typ, needs_D, needs_my);
+	case TYPE_FUNCTION:
+		// Functions should be handled differently (GO instead of DATA)
+		return 0;
+	default:
+		printf("Error: is_simple_type on unknown type %u\n", typ->typ);
+		return 0;
+	}
+}
+
+static int convert_type(string_t *dest, type_t *typ, int is_ret, int *needs_D, int *needs_my, string_t *obj_name) {
+	if (typ->is_atomic) {
+		printf("TODO: convert_type for atomic types\n");
+		return 0;
+	}
+	switch (typ->typ) {
+	case TYPE_BUILTIN: {
+		int has_char = 0;
+		char c;
+		switch (typ->val.builtin) {
+		case BTT_VOID: has_char = 1; c = 'v'; break;
+		case BTT_BOOL: has_char = 1; c = 'i'; break;
+		case BTT_CHAR: has_char = 1; c = 'c'; break;
+		case BTT_SCHAR: has_char = 1; c = 'c'; break;
+		case BTT_UCHAR: has_char = 1; c = 'C'; break;
+		case BTT_SHORT: has_char = 1; c = 'w'; break;
+		case BTT_SSHORT: has_char = 1; c = 'w'; break;
+		case BTT_USHORT: has_char = 1; c = 'W'; break;
+		case BTT_INT: has_char = 1; c = 'i'; break;
+		case BTT_SINT: has_char = 1; c = 'i'; break;
+		case BTT_UINT: has_char = 1; c = 'u'; break;
+		case BTT_LONG: has_char = 1; c = 'l'; break;
+		case BTT_SLONG: has_char = 1; c = 'l'; break;
+		case BTT_ULONG: has_char = 1; c = 'L'; break;
+		case BTT_LONGLONG: has_char = 1; c = 'I'; break;
+		case BTT_SLONGLONG: has_char = 1; c = 'I'; break;
+		case BTT_ULONGLONG: has_char = 1; c = 'U'; break;
+		case BTT_INT128: has_char = 1; c = 'H'; break; // TODO: Is 'H' for signed and unsigned?
+		case BTT_SINT128: has_char = 1; c = 'H'; break; // Is 'H' for signed and unsigned?
+		case BTT_UINT128: has_char = 1; c = 'H'; break; // Is 'H' for signed and unsigned?
+		case BTT_S8: has_char = 1; c = 'c'; break;
+		case BTT_U8: has_char = 1; c = 'C'; break;
+		case BTT_S16: has_char = 1; c = 'w'; break;
+		case BTT_U16: has_char = 1; c = 'W'; break;
+		case BTT_S32: has_char = 1; c = 'i'; break;
+		case BTT_U32: has_char = 1; c = 'u'; break;
+		case BTT_S64: has_char = 1; c = 'I'; break;
+		case BTT_U64: has_char = 1; c = 'U'; break;
+		case BTT_FLOAT: has_char = 1; c = 'f'; break;
+		case BTT_CFLOAT: has_char = 1; c = 'x'; break;
+		case BTT_IFLOAT: has_char = 1; c = 'f'; break;
+		case BTT_DOUBLE: has_char = 1; c = 'd'; break;
+		case BTT_CDOUBLE: has_char = 1; c = 'X'; break;
+		case BTT_IDOUBLE: has_char = 1; c = 'd'; break;
+		case BTT_LONGDOUBLE: *needs_D = 1; has_char = 1; c = 'D'; break;
+		case BTT_CLONGDOUBLE: *needs_D = 1; has_char = 1; c = 'Y'; break;
+		case BTT_ILONGDOUBLE: *needs_D = 1; has_char = 1; c = 'D'; break;
+		case BTT_VA_LIST: has_char = 1; c = 'A'; break;
+		default:
+			printf("Error: convert_type on unknown builtin %u\n", typ->val.builtin);
+			return 0;
+		}
+		if (has_char) {
+			if (!string_add_char(dest, c)) {
+				printf("Error: failed to add type char for %s\n", builtin2str[typ->val.builtin]);
+				return 0;
+			}
+			return 1;
+		} else {
+			printf("Internal error: unknown state builtin=%u\n", typ->val.builtin);
+			return 0;
+		} }
+	case TYPE_ARRAY:
+		printf("Error: convert_type on raw array\n");
+		return 0;
+	case TYPE_STRUCT_UNION:
+		if (!typ->is_validated || typ->is_incomplete) {
+			printf("Error: incomplete return type for %s\n", string_content(obj_name));
+			return 0;
+		}
+		if (is_ret) {
+			if (typ->szinfo.size <= 8) {
+				if (!string_add_char(dest, 'U')) {
+					printf("Error: failed to add type char for structure return\n");
+					return 0;
+				}
+				return 1;
+			} else if (typ->szinfo.size <= 16) {
+				if (!string_add_char(dest, 'H')) {
+					printf("Error: failed to add type char for large structure return\n");
+					return 0;
+				}
+				return 1;
+			} else {
+				if (!string_add_char(dest, 'p')) {
+					printf("Error: failed to add type char for very large structure return\n");
+					return 0;
+				}
+				return 1;
+			}
+		} else {
+			if (typ->val.st->nmembers == 1) {
+				return convert_type(dest, typ->val.st->members[0].typ, is_ret, needs_D, needs_my, obj_name);
+			}
+			printf("TODO: convert_type on structure as argument (%s)\n", string_content(obj_name));
+			return 0;
+		}
+	case TYPE_ENUM:
+		return convert_type(dest, typ->val.typ, is_ret, needs_D, needs_my, obj_name);
+	case TYPE_PTR:
+		if ((typ->val.typ->typ == TYPE_STRUCT_UNION) && typ->val.typ->val.st->tag && !strcmp(string_content(typ->val.typ->val.st->tag), "_IO_FILE")) {
+			if (!string_add_char(dest, 'S')) {
+				printf("Error: failed to add type char for %s\n", builtin2str[typ->val.builtin]);
+				return 0;
+			}
+			return 1;
+		}
+		if (is_simple_type_ptr_to(typ->val.typ, needs_D, needs_my)) {
+			if (!string_add_char(dest, 'p')) {
+				printf("Error: failed to add type char for simple pointer\n");
+				return 0;
+			}
+			return 1;
+		} else {
+			*needs_my = 1;
+			if (!string_add_char(dest, 'p')) {
+				printf("Error: failed to add type char for %s\n", builtin2str[typ->val.builtin]);
+				return 0;
+			}
+			return 1;
+		}
+	case TYPE_FUNCTION:
+		printf("Error: convert_type on raw function\n");
+		return 0;
+	default:
+		printf("Error: convert_type on unknown type %u\n", typ->typ);
+		return 0;
+	}
+}
+static int convert_type_post(string_t *dest, type_t *typ, string_t *obj_name) {
+	if (typ->is_atomic) {
+		printf("TODO: convert_type_post for atomic types\n");
+		return 0;
+	}
+	switch (typ->typ) {
+	case TYPE_BUILTIN: return 1;
+	case TYPE_ARRAY: return 1;
+	case TYPE_STRUCT_UNION:
+		if (!typ->is_validated || typ->is_incomplete) {
+			printf("Error: incomplete return type for %s\n", string_content(obj_name));
+			return 0;
+		}
+		if (typ->szinfo.size <= 16) {
+			return 1;
+		} else {
+			if (!string_add_char(dest, 'p')) {
+				printf("Error: failed to add type char for very large structure return as parameter\n");
+				return 0;
+			}
+			return 2;
+		}
+	case TYPE_ENUM: return 1;
+	case TYPE_PTR: return 1;
+	case TYPE_FUNCTION: return 1;
+	default:
+		printf("Error: convert_type_post on unknown type %u\n", typ->typ);
+		return 0;
+	}
+}
+
+int solve_request(request_t *req, type_t *typ) {
+	if (typ->typ == TYPE_FUNCTION) {
+		int needs_D = 0, needs_my = req->has_default && (req->def.rty == RQT_FUN_MY), needs_2 = 0;
+		int convert_post;
+		req->val.fun.typ = string_new();
+		if (!req->val.fun.typ) {
+			printf("Error: failed to create function type string\n");
+			return 0;
+		}
+		if (!convert_type(req->val.fun.typ, typ->val.fun.ret, 1, &needs_D, &needs_my, req->obj_name)) goto fun_fail;
+		if (!string_add_char(req->val.fun.typ, 'F')) {
+			printf("Error: failed to add convention char\n");
+			goto fun_fail;
+		}
+		if (req->has_default && (req->def.rty == RQT_FUN_MY) && (string_content(req->def.fun.typ)[2] == 'E')) {
+			if (!string_add_char(req->val.fun.typ, 'E')) {
+				printf("Error: failed to add emu char\n");
+				goto fun_fail;
+			}
+		}
+		convert_post = convert_type_post(req->val.fun.typ, typ->val.fun.ret, req->obj_name);
+		if (!convert_post) goto fun_fail;
+		if (typ->val.fun.nargs == (size_t)-1) {
+			printf("Warning: assuming empty specification is void specification\n");
+			if (convert_post == 1) {
+				if (!string_add_char(req->val.fun.typ, 'v')) {
+					printf("Error: failed to add void specification char\n");
+					goto fun_fail;
+				}
+			}
+		} else if (!typ->val.fun.nargs && !typ->val.fun.has_varargs) {
+			if (convert_post == 1) {
+				if (!string_add_char(req->val.fun.typ, 'v')) {
+					printf("Error: failed to add void specification char\n");
+					goto fun_fail;
+				}
+			}
+		} else {
+			for (size_t i = 0; i < typ->val.fun.nargs; ++i) {
+				if (!convert_type(req->val.fun.typ, typ->val.fun.args[i], 0, &needs_D, &needs_my, req->obj_name)) goto fun_fail;
+			}
+			if (typ->val.fun.has_varargs) {
+				if (!string_add_char(req->val.fun.typ, 'V')) {
+					printf("Error: failed to add type char for %s\n", builtin2str[typ->val.builtin]);
+					goto fun_fail;
+				}
+			}
+		}
+		
+	// fun_succ:
+		if (req->has_default && (req->def.rty == RQT_FUN_2) && !needs_my) {
+			needs_2 = 1;
+			req->val.fun.fun2 = string_dup(req->def.fun.fun2);
+			if (!req->val.fun.fun2) {
+				printf("Error: failed to duplicate string (request for function %s with default redirection)\n", string_content(req->obj_name));
+				return 0;
+			}
+		} else if (req->has_default && (req->def.rty == RQT_FUN_D) && !needs_my) {
+			needs_2 = 0;
+			req->val.fun.fun2 = string_dup(req->def.fun.fun2);
+			if (!req->val.fun.fun2) {
+				printf("Error: failed to duplicate string (request for function %s with long double types)\n", string_content(req->obj_name));
+				return 0;
+			}
+		} else if (!needs_my && needs_D) {
+			req->val.fun.fun2 = string_new();
+			if (!req->val.fun.fun2) {
+				printf("Error: failed to create empty string (request for function %s with long double types)\n", string_content(req->obj_name));
+				return 0;
+			}
+		}
+		req->val.rty =
+			needs_my ? RQT_FUN_MY :
+			needs_2 ? RQT_FUN_2 :
+			needs_D ? RQT_FUN_D : RQT_FUN;
+		req->has_val = 1;
+		return 1;
+		
+	fun_fail:
+		string_del(req->val.fun.typ);
+		return 0;
+	} else {
+		int needs_D = 0, needs_my = req->has_default && (req->def.rty == RQT_FUN_MY);
+		if (is_simple_type(typ, &needs_D, &needs_my)) {
+			// TODO: Hmm...
+			req->val.rty = needs_my ? RQT_DATAM : req->has_default ? req->def.rty : req->weak ? RQT_DATAV : RQT_DATA;
+			req->val.dat.has_size = 1;
+			req->val.dat.sz = typ->szinfo.size;
+			req->has_val = 1;
+			return 1;
+		} else {
+			printf("Error: TODO: solve_request for data %s with non-simple type ", string_content(req->obj_name));
+			type_print(typ);
+			printf("\n");
+			return 0;
+		}
+	}
+}
+int solve_request_map(request_t *req, khash_t(type_map) *decl_map) {
+	khiter_t it = kh_get(type_map, decl_map, string_content(req->obj_name));
+	if (it == kh_end(decl_map)) {
+		if (string_content(req->obj_name)[0] != '_') {
+			printf("Error: %s was not declared\n", string_content(req->obj_name));
+		}
+		return 0;
+	}
+	return solve_request(req, kh_val(decl_map, it));
+}
+int solve_requests(VECTOR(requests) *reqs, khash_t(type_map) *decl_map) {
+	int ret = 1;
+	vector_for(requests, req, reqs) {
+		if (!solve_request_map(req, decl_map)) ret = 0;
+	}
+	return ret;
+}
diff --git a/wrapperhelper/src/generator.h b/wrapperhelper/src/generator.h
new file mode 100644
index 00000000..ef27ad0a
--- /dev/null
+++ b/wrapperhelper/src/generator.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#ifndef GENERATOR_H
+#define GENERATOR_H
+
+#include <stdio.h>
+
+#include "cstring.h"
+#include "lang.h"
+
+typedef struct request_s {
+	string_t *obj_name;
+	_Bool has_default, default_comment;
+	_Bool has_val;
+	_Bool weak;
+	struct {
+		enum request_type_e {
+			RQT_FUN,
+			RQT_FUN_2,
+			RQT_FUN_MY,
+			RQT_FUN_D,
+			
+			RQT_DATA,
+			RQT_DATAV,
+			RQT_DATAB,
+			RQT_DATAM,
+		} rty;
+		union {
+			struct {
+				string_t *typ;
+				string_t *fun2;
+			} fun;
+			struct {
+				int has_size;
+				size_t sz;
+			} dat;
+		};
+	} def, val;
+} request_t;
+VECTOR_DECLARE(requests, request_t)
+void request_print(request_t *req);
+void request_print_check(request_t *req);
+void output_from_requests(FILE *f, VECTOR(requests) *reqs);
+
+VECTOR(requests) *requests_from_file(const char *filename, FILE *f); // Takes ownership of f
+int solve_request(request_t *req, type_t *typ);
+int solve_request_map(request_t *req, khash_t(type_map) *decl_map);
+int solve_requests(VECTOR(requests) *reqs, khash_t(type_map) *decl_map);
+
+#endif // GENERATOR_H
diff --git a/wrapperhelper/src/khash.h b/wrapperhelper/src/khash.h
new file mode 100644
index 00000000..57a98bb4
--- /dev/null
+++ b/wrapperhelper/src/khash.h
@@ -0,0 +1,699 @@
+/* The MIT License
+
+   Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
+
+   Permission is hereby granted, free of charge, to any person obtaining
+   a copy of this software and associated documentation files (the
+   "Software"), to deal in the Software without restriction, including
+   without limitation the rights to use, copy, modify, merge, publish,
+   distribute, sublicense, and/or sell copies of the Software, and to
+   permit persons to whom the Software is furnished to do so, subject to
+   the following conditions:
+
+   The above copyright notice and this permission notice shall be
+   included in all copies or substantial portions of the Software.
+
+   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+   SOFTWARE.
+*/
+
+/*
+  An example:
+
+#include "khash.h"
+KHASH_MAP_INIT_INT(32, char)
+int main() {
+	int ret, is_missing;
+	khiter_t k;
+	khash_t(32) *h = kh_init(32);
+	k = kh_put(32, h, 5, &ret);
+	kh_value(h, k) = 10;
+	k = kh_get(32, h, 10);
+	is_missing = (k == kh_end(h));
+	k = kh_get(32, h, 5);
+	kh_del(32, h, k);
+	for (k = kh_begin(h); k != kh_end(h); ++k)
+		if (kh_exist(h, k)) kh_value(h, k) = 1;
+	kh_destroy(32, h);
+	return 0;
+}
+*/
+
+/*
+  2013-05-02 (0.2.8):
+
+	* Use quadratic probing. When the capacity is power of 2, stepping function
+	  i*(i+1)/2 guarantees to traverse each bucket. It is better than double
+	  hashing on cache performance and is more robust than linear probing.
+
+	  In theory, double hashing should be more robust than quadratic probing.
+	  However, my implementation is probably not for large hash tables, because
+	  the second hash function is closely tied to the first hash function,
+	  which reduce the effectiveness of double hashing.
+
+	Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php
+
+  2011-12-29 (0.2.7):
+
+    * Minor code clean up; no actual effect.
+
+  2011-09-16 (0.2.6):
+
+	* The capacity is a power of 2. This seems to dramatically improve the
+	  speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
+
+	   - http://code.google.com/p/ulib/
+	   - http://nothings.org/computer/judy/
+
+	* Allow to optionally use linear probing which usually has better
+	  performance for random input. Double hashing is still the default as it
+	  is more robust to certain non-random input.
+
+	* Added Wang's integer hash function (not used by default). This hash
+	  function is more robust to certain non-random input.
+
+  2011-02-14 (0.2.5):
+
+    * Allow to declare global functions.
+
+  2009-09-26 (0.2.4):
+
+    * Improve portability
+
+  2008-09-19 (0.2.3):
+
+	* Corrected the example
+	* Improved interfaces
+
+  2008-09-11 (0.2.2):
+
+	* Improved speed a little in kh_put()
+
+  2008-09-10 (0.2.1):
+
+	* Added kh_clear()
+	* Fixed a compiling error
+
+  2008-09-02 (0.2.0):
+
+	* Changed to token concatenation which increases flexibility.
+
+  2008-08-31 (0.1.2):
+
+	* Fixed a bug in kh_get(), which has not been tested previously.
+
+  2008-08-31 (0.1.1):
+
+	* Added destructor
+*/
+
+
+#ifndef __AC_KHASH_H
+#define __AC_KHASH_H
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wsign-conversion"
+#pragma GCC diagnostic ignored "-Wconversion"
+
+/*!
+  @header
+
+  Generic hash table library.
+ */
+
+#define AC_VERSION_KHASH_H "0.2.8"
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+/* compiler specific configuration */
+
+#if UINT_MAX == 0xffffffffu
+typedef unsigned int khint32_t;
+#elif ULONG_MAX == 0xffffffffu
+typedef unsigned long khint32_t;
+#endif
+
+#if ULONG_MAX == ULLONG_MAX
+typedef unsigned long khint64_t;
+#else
+typedef unsigned long long khint64_t;
+#endif
+
+#ifdef _MSC_VER
+#define kh_inline __inline
+#else
+#define kh_inline inline
+#endif
+
+typedef khint32_t khint_t;
+typedef khint_t khiter_t;
+
+#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
+#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
+#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
+#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1u<<((i&0xfU)<<1)))
+#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2u<<((i&0xfU)<<1)))
+#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3u<<((i&0xfU)<<1)))
+#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1u<<((i&0xfU)<<1))
+
+#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
+
+#ifndef kroundup32
+#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
+#endif
+
+#ifndef kcalloc
+#define kcalloc(N,Z) calloc(N,Z)
+#endif
+#ifndef kmalloc
+#define kmalloc(Z) malloc(Z)
+#endif
+#ifndef krealloc
+#define krealloc(P,Z) realloc(P,Z)
+#endif
+#ifndef kfree
+#define kfree(P) free(P)
+#endif
+
+static const double __ac_HASH_UPPER = 0.77;
+
+#define __KHASH_TYPE(name, khkey_t, khval_t) \
+	typedef struct kh_##name##_s{ \
+		khint_t n_buckets, size, n_occupied, upper_bound; \
+		khint32_t *flags; \
+		khkey_t *keys; \
+		khval_t *vals; \
+	} kh_##name##_t;
+
+#define __KHASH_PROTOTYPES(name, khkey_t, khval_t)	 					\
+	extern kh_##name##_t *kh_init_##name(void);							\
+	extern void kh_destroy_##name(kh_##name##_t *h);					\
+	extern void kh_clear_##name(kh_##name##_t *h);						\
+	extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); 	\
+	extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
+	extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
+	extern void kh_del_##name(kh_##name##_t *h, khint_t x);
+
+#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+	SCOPE kh_##name##_t *kh_init_##name(void) {							\
+		return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t));		\
+	}																	\
+	SCOPE void kh_destroy_##name(kh_##name##_t *h)						\
+	{																	\
+		if (h) {														\
+			kfree((void *)h->keys); kfree(h->flags);					\
+			kfree((void *)h->vals);										\
+			kfree(h);													\
+		}																\
+	}																	\
+	SCOPE void kh_clear_##name(kh_##name##_t *h)						\
+	{																	\
+		if (h && h->flags) {											\
+			memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
+			h->size = h->n_occupied = 0;								\
+		}																\
+	}																	\
+	__attribute__((pure)) SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
+	{																	\
+		if (h->n_buckets) {												\
+			khint_t k, i, last, mask, step = 0; \
+			mask = h->n_buckets - 1;									\
+			k = __hash_func(key); i = k & mask;							\
+			last = i; \
+			while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
+				i = (i + (++step)) & mask; \
+				if (i == last) return h->n_buckets;						\
+			}															\
+			return __ac_iseither(h->flags, i)? h->n_buckets : i;		\
+		} else return 0;												\
+	}																	\
+	SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
+	{ /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
+		khint32_t *new_flags = 0;										\
+		khint_t j = 1;													\
+		{																\
+			kroundup32(new_n_buckets); 									\
+			if (new_n_buckets < 4) new_n_buckets = 4;					\
+			if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0;	/* requested size is too small */ \
+			else { /* hash table size to be changed (shrink or expand); rehash */ \
+				new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t));	\
+				if (!new_flags) return -1;								\
+				memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
+				if (h->n_buckets < new_n_buckets) {	/* expand */		\
+					khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
+					if (!new_keys) { kfree(new_flags); return -1; }		\
+					h->keys = new_keys;									\
+					if (kh_is_map) {									\
+						khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
+						if (!new_vals) { kfree(new_flags); return -1; }	\
+						h->vals = new_vals;								\
+					}													\
+				} /* otherwise shrink */								\
+			}															\
+		}																\
+		if (j) { /* rehashing is needed */								\
+			for (j = 0; j != h->n_buckets; ++j) {						\
+				if (__ac_iseither(h->flags, j) == 0) {					\
+					khkey_t key = h->keys[j];							\
+					khval_t val;										\
+					khint_t new_mask;									\
+					new_mask = new_n_buckets - 1; 						\
+					if (kh_is_map) val = h->vals[j];					\
+					__ac_set_isdel_true(h->flags, j);					\
+					while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
+						khint_t k, i, step = 0; \
+						k = __hash_func(key);							\
+						i = k & new_mask;								\
+						while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \
+						__ac_set_isempty_false(new_flags, i);			\
+						if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
+							{ khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
+							if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
+							__ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
+						} else { /* write the element and jump out of the loop */ \
+							h->keys[i] = key;							\
+							if (kh_is_map) h->vals[i] = val;			\
+							break;										\
+						}												\
+					}													\
+				}														\
+			}															\
+			if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
+				h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
+				if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
+			}															\
+			kfree(h->flags); /* free the working space */				\
+			h->flags = new_flags;										\
+			h->n_buckets = new_n_buckets;								\
+			h->n_occupied = h->size;									\
+			h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
+		}																\
+		return 0;														\
+	}																	\
+	SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
+	{																	\
+		khint_t x;														\
+		if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
+			if (h->n_buckets > (h->size<<1)) {							\
+				if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
+					*ret = -1; return h->n_buckets;						\
+				}														\
+			} else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
+				*ret = -1; return h->n_buckets;							\
+			}															\
+		} /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
+		{																\
+			khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
+			x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
+			if (__ac_isempty(h->flags, i)) x = i; /* for speed up */	\
+			else {														\
+				last = i; \
+				while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
+					if (__ac_isdel(h->flags, i)) site = i;				\
+					i = (i + (++step)) & mask; \
+					if (i == last) { x = site; break; }					\
+				}														\
+				if (x == h->n_buckets) {								\
+					if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
+					else x = i;											\
+				}														\
+			}															\
+		}																\
+		if (__ac_isempty(h->flags, x)) { /* not present at all */		\
+			h->keys[x] = key;											\
+			__ac_set_isboth_false(h->flags, x);							\
+			++h->size; ++h->n_occupied;									\
+			*ret = 1;													\
+		} else if (__ac_isdel(h->flags, x)) { /* deleted */				\
+			h->keys[x] = key;											\
+			__ac_set_isboth_false(h->flags, x);							\
+			++h->size;													\
+			*ret = 2;													\
+		} else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
+		return x;														\
+	}																	\
+	SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x)				\
+	{																	\
+		if (x != h->n_buckets && !__ac_iseither(h->flags, x)) {			\
+			__ac_set_isdel_true(h->flags, x);							\
+			--h->size;													\
+		}																\
+	}
+
+#define KHASH_DECLARE(name, khkey_t, khval_t)		 					\
+	__KHASH_TYPE(name, khkey_t, khval_t) 								\
+	__KHASH_PROTOTYPES(name, khkey_t, khval_t)
+
+#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+	__KHASH_TYPE(name, khkey_t, khval_t) 								\
+	__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
+
+#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
+	KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
+
+/* --- BEGIN OF HASH FUNCTIONS --- */
+
+/*! @function
+  @abstract     Integer hash function
+  @param  key   The integer [khint32_t]
+  @return       The hash value [khint_t]
+ */
+#define kh_int_hash_func(key) (khint32_t)(key)
+/*! @function
+  @abstract     Integer comparison function
+ */
+#define kh_int_hash_equal(a, b) ((a) == (b))
+/*! @function
+  @abstract     64-bit integer hash function
+  @param  key   The integer [khint64_t]
+  @return       The hash value [khint_t]
+ */
+#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
+/*! @function
+  @abstract     64-bit integer comparison function
+ */
+#define kh_int64_hash_equal(a, b) ((a) == (b))
+/*! @function
+  @abstract     const char* hash function
+  @param  s     Pointer to a null terminated string
+  @return       The hash value
+ */
+__attribute__((pure)) static kh_inline khint_t __ac_X31_hash_string(const char *s)
+{
+	khint_t h = (khint_t)*s;
+	if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
+	return h;
+}
+/*! @function
+  @abstract     Another interface to const char* hash function
+  @param  key   Pointer to a null terminated string [const char*]
+  @return       The hash value [khint_t]
+ */
+#define kh_str_hash_func(key) __ac_X31_hash_string(key)
+/*! @function
+  @abstract     Const char* comparison function
+ */
+#define kh_str_hash_equal(a, b) ((!a && !b) || (a && b && strcmp(a, b) == 0))
+
+static kh_inline khint_t __ac_Wang_hash(khint_t key)
+{
+    key += ~(key << 15);
+    key ^=  (key >> 10);
+    key +=  (key << 3);
+    key ^=  (key >> 6);
+    key += ~(key << 11);
+    key ^=  (key >> 16);
+    return key;
+}
+#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key)
+
+/* --- END OF HASH FUNCTIONS --- */
+
+/* Other convenient macros... */
+
+/*!
+  @abstract Type of the hash table.
+  @param  name  Name of the hash table [symbol]
+ */
+#define khash_t(name) kh_##name##_t
+
+/*! @function
+  @abstract     Initiate a hash table.
+  @param  name  Name of the hash table [symbol]
+  @return       Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_init(name) kh_init_##name()
+
+/*! @function
+  @abstract     Destroy a hash table.
+  @param  name  Name of the hash table [symbol]
+  @param  h     Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_destroy(name, h) kh_destroy_##name(h)
+
+/*! @function
+  @abstract     Reset a hash table without deallocating memory.
+  @param  name  Name of the hash table [symbol]
+  @param  h     Pointer to the hash table [khash_t(name)*]
+ */
+#define kh_clear(name, h) kh_clear_##name(h)
+
+/*! @function
+  @abstract     Resize a hash table.
+  @param  name  Name of the hash table [symbol]
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @param  s     New size [khint_t]
+ */
+#define kh_resize(name, h, s) kh_resize_##name(h, s)
+
+/*! @function
+  @abstract     Insert a key to the hash table.
+  @param  name  Name of the hash table [symbol]
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @param  k     Key [type of keys]
+  @param  r     Extra return code: 0 if the key is present in the hash table;
+                1 if the bucket is empty (never used); 2 if the element in
+				the bucket has been deleted [int*]
+  @return       Iterator to the inserted element [khint_t]
+ */
+#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
+
+/*! @function
+  @abstract     Retrieve a key from the hash table.
+  @param  name  Name of the hash table [symbol]
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @param  k     Key [type of keys]
+  @return       Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
+ */
+#define kh_get(name, h, k) kh_get_##name(h, k)
+
+/*! @function
+  @abstract     Remove a key from the hash table.
+  @param  name  Name of the hash table [symbol]
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @param  k     Iterator to the element to be deleted [khint_t]
+ */
+#define kh_del(name, h, k) kh_del_##name(h, k)
+
+/*! @function
+  @abstract     Test whether a bucket contains data.
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @param  x     Iterator to the bucket [khint_t]
+  @return       1 if containing data; 0 otherwise [int]
+ */
+#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
+
+/*! @function
+  @abstract     Get key given an iterator
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @param  x     Iterator to the bucket [khint_t]
+  @return       Key [type of keys]
+ */
+#define kh_key(h, x) ((h)->keys[x])
+
+/*! @function
+  @abstract     Get value given an iterator
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @param  x     Iterator to the bucket [khint_t]
+  @return       Value [type of values]
+  @discussion   For hash sets, calling this results in segfault.
+ */
+#define kh_val(h, x) ((h)->vals[x])
+
+/*! @function
+  @abstract     Alias of kh_val()
+ */
+#define kh_value(h, x) ((h)->vals[x])
+
+/*! @function
+  @abstract     Get the start iterator
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @return       The start iterator [khint_t]
+ */
+#define kh_begin(h) (khint_t)(0)
+
+/*! @function
+  @abstract     Get the end iterator
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @return       The end iterator [khint_t]
+ */
+#define kh_end(h) ((h)->n_buckets)
+
+/*! @function
+  @abstract     Get the number of elements in the hash table
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @return       Number of elements in the hash table [khint_t]
+ */
+#define kh_size(h) ((h)->size)
+
+/*! @function
+  @abstract     Get the number of buckets in the hash table
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @return       Number of buckets in the hash table [khint_t]
+ */
+#define kh_n_buckets(h) ((h)->n_buckets)
+
+/*! @function
+  @abstract     Iterate over the entries in the hash table
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @param  kvar  Variable to which key will be assigned
+  @param  vvar  Variable to which value will be assigned
+  @param  code  Block of code to execute
+ */
+#define kh_foreach(h, kvar, vvar, code) { khint_t __i;		\
+	for (__i = kh_begin(h); __i != kh_end(h); ++__i) {		\
+		if (!kh_exist(h,__i)) continue;						\
+		(kvar) = kh_key(h,__i);								\
+		(vvar) = kh_val(h,__i);								\
+		code;												\
+	} }
+
+/*! @function
+  @abstract     Iterate over the entries in the hash table
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @param  kvar  Variable to which key will be assigned
+  @param  code  Block of code to execute
+ */
+#define kh_foreach_key(h, kvar, code) { khint_t __i;           	\
+       for (__i = kh_begin(h); __i != kh_end(h); ++__i) {		\
+			if (!kh_exist(h,__i)) continue;                  	\
+               (kvar) = kh_key(h,__i);                          \
+               code;                                            \
+       } }
+
+/*! @function
+  @abstract     Iterate over the values in the hash table
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @param  vvar  Variable to which value will be assigned
+  @param  code  Block of code to execute
+ */
+#define kh_foreach_value(h, vvar, code) { khint_t __i;		\
+	for (__i = kh_begin(h); __i != kh_end(h); ++__i) {		\
+		if (!kh_exist(h,__i)) continue;						\
+		(vvar) = kh_val(h,__i);								\
+		code;												\
+	} }
+
+/*! @function
+  @abstract     Iterate over the entries in the hash table
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @param  kvar  Variable to which key will be assigned
+  @param  rvar  Variable to which value will be assigned
+  @param  code  Block of code to execute
+ */
+#define kh_foreach_key_value_ref(h, kvar, rvar, code) { khint_t __i;	\
+	for (__i = kh_begin(h); __i != kh_end(h); ++__i) {					\
+		if (!kh_exist(h,__i)) continue;									\
+		(kvar) = kh_key(h,__i);											\
+		(rvar) = &kh_val(h,__i);										\
+		code;															\
+	} }
+
+/*! @function
+  @abstract     Iterate over the values in the hash table
+  @param  h     Pointer to the hash table [khash_t(name)*]
+  @param  rvar  Variable to which value will be assigned
+  @param  code  Block of code to execute
+ */
+#define kh_foreach_value_ref(h, rvar, code) { khint_t __i;	\
+	for (__i = kh_begin(h); __i != kh_end(h); ++__i) {		\
+		if (!kh_exist(h,__i)) continue;						\
+		(rvar) = &kh_val(h,__i);							\
+		code;												\
+	} }
+
+/* More conenient interfaces */
+
+/*! @function
+  @abstract     Instantiate a hash set containing integer keys
+  @param  name  Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_INT(name)										\
+	KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
+
+#define KHASH_SET_DECLARE_INT(name)										\
+	KHASH_DECLARE(name, khint32_t, char)
+
+#define KHASH_SET_IMPL_INT(name)										\
+	__KHASH_IMPL(name, , khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
+
+/*! @function
+  @abstract     Instantiate a hash map containing integer keys
+  @param  name  Name of the hash table [symbol]
+  @param  khval_t  Type of values [type]
+ */
+#define KHASH_MAP_INIT_INT(name, khval_t)								\
+	KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
+
+#define KHASH_MAP_DECLARE_INT(name, khval_t)								\
+	KHASH_DECLARE(name, khint32_t, khval_t)
+
+#define KHASH_MAP_IMPL_INT(name, khval_t)								\
+	__KHASH_IMPL(name, , khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
+
+/*! @function
+  @abstract     Instantiate a hash map containing 64-bit integer keys
+  @param  name  Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_INT64(name)										\
+	KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
+
+#define KHASH_SET_DECLARE_INT64(name)										\
+	KHASH_DECLARE(name, khint64_t, char)
+
+#define KHASH_SET_IMPL_INT64(name)										\
+	__KHASH_IMPL(name, , khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
+
+/*! @function
+  @abstract     Instantiate a hash map containing 64-bit integer keys
+  @param  name  Name of the hash table [symbol]
+  @param  khval_t  Type of values [type]
+ */
+#define KHASH_MAP_INIT_INT64(name, khval_t)								\
+	KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
+
+#define KHASH_MAP_DECLARE_INT64(name, khval_t)								\
+	KHASH_DECLARE(name, khint64_t, khval_t)
+
+#define KHASH_MAP_IMPL_INT64(name, khval_t)								\
+	__KHASH_IMPL(name, , khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
+
+typedef const char *kh_cstr_t;
+/*! @function
+  @abstract     Instantiate a hash map containing const char* keys
+  @param  name  Name of the hash table [symbol]
+ */
+#define KHASH_SET_INIT_STR(name)										\
+	KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
+
+#define KHASH_SET_DECLARE_STR(name)										\
+	KHASH_DECLARE(name, kh_cstr_t, char)
+
+#define KHASH_SET_IMPL_STR(name)										\
+	__KHASH_IMPL(name, , kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
+
+/*! @function
+  @abstract     Instantiate a hash map containing const char* keys
+  @param  name  Name of the hash table [symbol]
+  @param  khval_t  Type of values [type]
+ */
+#define KHASH_MAP_INIT_STR(name, khval_t)								\
+	KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
+
+#define KHASH_MAP_DECLARE_STR(name, khval_t)								\
+	KHASH_DECLARE(name, kh_cstr_t, khval_t)
+
+#define KHASH_MAP_IMPL_STR(name, khval_t)								\
+	__KHASH_IMPL(name, , kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
+
+#pragma GCC diagnostic pop
+
+#endif /* __AC_KHASH_H */
diff --git a/wrapperhelper/src/lang.c b/wrapperhelper/src/lang.c
new file mode 100644
index 00000000..9ca51b15
--- /dev/null
+++ b/wrapperhelper/src/lang.c
@@ -0,0 +1,1164 @@
+#include "lang.h"
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#if 1
+#define DISP_ADDR_FMT ""
+#define DISP_ADDR_ARG(v) 
+#else
+#define DISP_ADDR_FMT "%p "
+#define DISP_ADDR_ARG(v) v,
+#endif
+
+void preproc_token_del(preproc_token_t *tok) {
+	switch (tok->tokt) {
+	case PPTOK_IDENT:
+	case PPTOK_IDENT_UNEXP:
+	case PPTOK_NUM:
+		string_del(tok->tokv.str);
+		break;
+	case PPTOK_STRING:
+	case PPTOK_INCL:
+		string_del(tok->tokv.sstr);
+		break;
+	case PPTOK_INVALID:
+	case PPTOK_SYM:
+	case PPTOK_NEWLINE:
+	case PPTOK_BLANK:
+	case PPTOK_START_LINE_COMMENT:
+	case PPTOK_EOF:
+		break;
+	}
+}
+
+VECTOR_IMPL(preproc, preproc_token_del)
+
+void proc_token_del(proc_token_t *tok) {
+	switch (tok->tokt) {
+	case PTOK_IDENT:
+	case PTOK_NUM:
+		string_del(tok->tokv.str);
+		break;
+	case PTOK_STRING:
+		string_del(tok->tokv.sstr);
+		break;
+	case PTOK_PRAGMA:
+		switch (tok->tokv.pragma.typ) {
+		case PRAGMA_MARK_SIMPLE:
+			string_del(tok->tokv.pragma.val);
+			break;
+		case PRAGMA_ALLOW_INTS:
+			break;
+		}
+		break;
+	case PTOK_INVALID:
+	case PTOK_KEYWORD:
+	case PTOK_SYM:
+	case PTOK_EOF:
+		break;
+	}
+}
+
+VECTOR_IMPL(proc, proc_token_del)
+
+const char *sym2str[LAST_SYM + 1] = {
+	[SYM_LBRACKET] = "{",
+	[SYM_RBRACKET] = "}",
+	[SYM_LSQBRACKET] = "[",
+	[SYM_RSQBRACKET] = "]",
+	[SYM_LPAREN] = "(",
+	[SYM_RPAREN] = ")",
+	[SYM_HASH] = "#",
+	[SYM_HASHHASH] = "##",
+	[SYM_SEMICOLON] = ";",
+	[SYM_COLON] = ":",
+	[SYM_COLONCOLON] = "::",
+	[SYM_VARIADIC] = "...",
+	[SYM_QUESTION] = "?",
+	[SYM_DOT] = ".",
+	[SYM_DASHGT] = "->",
+	[SYM_TILDE] = "~",
+	[SYM_EXCL] = "!",
+	[SYM_PLUS] = "+",
+	[SYM_DASH] = "-",
+	[SYM_STAR] = "*",
+	[SYM_SLASH] = "/",
+	[SYM_PERCENT] = "%",
+	[SYM_HAT] = "^",
+	[SYM_AMP] = "&",
+	[SYM_PIPE] = "|",
+	[SYM_EQ] = "=",
+	[SYM_PLUSEQ] = "+=",
+	[SYM_DASHEQ] = "-=",
+	[SYM_STAREQ] = "*=",
+	[SYM_SLASHEQ] = "/=",
+	[SYM_PERCENTEQ] = "%=",
+	[SYM_HATEQ] = "^=",
+	[SYM_AMPEQ] = "&=",
+	[SYM_PIPEEQ] = "|=",
+	[SYM_EQEQ] = "==",
+	[SYM_EXCLEQ] = "!=",
+	[SYM_LT] = "<",
+	[SYM_GT] = ">",
+	[SYM_LTEQ] = "<=",
+	[SYM_GTEQ] = ">=",
+	[SYM_AMPAMP] = "&&",
+	[SYM_PIPEPIPE] = "||",
+	[SYM_LTLT] = "<<",
+	[SYM_GTGT] = ">>",
+	[SYM_LTLTEQ] = "<<=",
+	[SYM_GTGTEQ] = ">>=",
+	[SYM_PLUSPLUS] = "++",
+	[SYM_DASHDASH] = "--",
+	[SYM_COMMA] = ",",
+};
+
+void preproc_token_print(const preproc_token_t *tok) {
+	switch (tok->tokt) {
+	case PPTOK_INVALID:
+		printf("Token: %7s %hhd (%c)\n", "#INVAL#", tok->tokv.c, (tok->tokv.c >= 0x20) && (tok->tokv.c < 0x7F) ? tok->tokv.c : '?');
+		break;
+	case PPTOK_IDENT:
+		printf("Token: %7s '%s'\n", "IDENT", string_content(tok->tokv.str));
+		break;
+	case PPTOK_IDENT_UNEXP:
+		printf("Token: %7s '%s'\n", "IDENT'", string_content(tok->tokv.str));
+		break;
+	case PPTOK_NUM:
+		printf("Token: %7s '%s'\n", "NUM", string_content(tok->tokv.str));
+		break;
+	case PPTOK_STRING:
+		printf("Token: %7s %c%s%c\n", "STRING",
+			tok->tokv.sisstr ? '"' : '\'', string_content(tok->tokv.sstr), tok->tokv.sisstr ? '"' : '\'');
+		break;
+	case PPTOK_INCL:
+		printf("Token: %7s %c%s%c\n", "INCL",
+			tok->tokv.sisstr ? '"' : '<', string_content(tok->tokv.sstr), tok->tokv.sisstr ? '"' : '>');
+		break;
+	case PPTOK_SYM:
+		printf("Token: %7s  %-3s (%u)\n", "SYM", sym2str[tok->tokv.sym], tok->tokv.sym);
+		break;
+	case PPTOK_NEWLINE:
+		printf("Token: %7s\n", "NEWLINE");
+		break;
+	case PPTOK_BLANK:
+		printf("Token: %7s\n", "\e[2;31m(blank)\e[m");
+		break;
+	case PPTOK_START_LINE_COMMENT:
+		printf("Token: %7s\n", "\e[2;31m( // ) \e[m");
+		break;
+	case PPTOK_EOF:
+		printf("Token: %7s\n", "EOF");
+		break;
+	default:
+		printf("Token: ??? %u\n", tok->tokt);
+	}
+}
+
+int preproc_token_isend(const preproc_token_t *tok) {
+	switch (tok->tokt) {
+	case PPTOK_IDENT:
+	case PPTOK_IDENT_UNEXP:
+	case PPTOK_NUM:
+	case PPTOK_STRING:
+	case PPTOK_INCL:
+	case PPTOK_SYM:
+	case PPTOK_NEWLINE:
+	case PPTOK_BLANK:
+	case PPTOK_START_LINE_COMMENT:
+		return 0;
+	case PPTOK_INVALID:
+	case PPTOK_EOF:
+	default:
+		return 1;
+	}
+}
+
+const char *kw2str[LAST_KEYWORD + 1] = {
+	[KW_ALIGNAS] = "_Alignas",
+	[KW_ALIGNOF] = "_Alignof",
+	[KW_ATOMIC] = "_Atomic",
+	[KW_AUTO] = "auto",
+	[KW_BOOL] = "_Bool",
+	[KW_BREAK] = "break",
+	[KW_CASE] = "case",
+	[KW_CHAR] = "char",
+	[KW_COMPLEX] = "_Complex",
+	[KW_CONST] = "const",
+	[KW_CONTINUE] = "continue",
+	[KW_DEFAULT] = "default",
+	[KW_DO] = "do",
+	[KW_DOUBLE] = "double",
+	[KW_ELSE] = "else",
+	[KW_ENUM] = "enum",
+	[KW_EXTERN] = "extern",
+	[KW_FLOAT] = "float",
+	[KW_FOR] = "for",
+	[KW_GENERIC] = "_Generic",
+	[KW_GOTO] = "goto",
+	[KW_IF] = "if",
+	[KW_IMAGINARY] = "_Imaginary",
+	[KW_INLINE] = "inline",
+	[KW_INT] = "int",
+	[KW_INT128] = "__int128",
+	[KW_LONG] = "long",
+	[KW_NORETURN] = "_Noreturn",
+	[KW_REGISTER] = "register",
+	[KW_RESTRICT] = "restrict",
+	[KW_RETURN] = "return",
+	[KW_SHORT] = "short",
+	[KW_SIGNED] = "signed",
+	[KW_SIZEOF] = "sizeof",
+	[KW_STATIC] = "static",
+	[KW_STATIC_ASSERT] = "_Static_assert",
+	[KW_STRUCT] = "struct",
+	[KW_SWITCH] = "switch",
+	[KW_THREAD_LOCAL] = "_Thread_local",
+	[KW_TYPEDEF] = "typedef",
+	[KW_UNION] = "union",
+	[KW_UNSIGNED] = "unsigned",
+	[KW_VOID] = "void",
+	[KW_VOLATILE] = "volatile",
+	[KW_WHILE] = "while",
+};
+
+void proc_token_print(const proc_token_t *tok) {
+	switch (tok->tokt) {
+	case PTOK_INVALID:
+		printf("Token: %7s %hhd (%c)\n", "#INVAL#", tok->tokv.c, (tok->tokv.c >= 0x20) && (tok->tokv.c < 0x7F) ? tok->tokv.c : '?');
+		break;
+	case PTOK_IDENT:
+		printf("Token: %7s '%s'\n", "IDENT", string_content(tok->tokv.str));
+		break;
+	case PTOK_KEYWORD:
+		printf("Token: %7s '%s' (%u)\n", "KEYWORD", kw2str[tok->tokv.kw], tok->tokv.kw);
+		break;
+	case PTOK_NUM:
+		printf("Token: %7s '%s'\n", "NUM", string_content(tok->tokv.str));
+		break;
+	case PTOK_STRING:
+		printf("Token: %7s %c%s%c\n", "STRING",
+			tok->tokv.sisstr ? '"' : '\'', string_content(tok->tokv.sstr), tok->tokv.sisstr ? '"' : '\'');
+		break;
+	case PTOK_SYM:
+		printf("Token: %7s  %-3s (%u)\n", "SYM", sym2str[tok->tokv.sym], tok->tokv.sym);
+		break;
+	case PTOK_PRAGMA:
+		switch (tok->tokv.pragma.typ) {
+		case PRAGMA_ALLOW_INTS:
+			printf("Token: %7s Allow ints\n", "PRAGMA");
+			break;
+		case PRAGMA_MARK_SIMPLE:
+			printf("Token: %7s Mark simple: %s\n", "PRAGMA", string_content(tok->tokv.pragma.val));
+			break;
+		default:
+			printf("Token: %7s ???\n", "PRAGMA");
+		}
+		break;
+	case PTOK_EOF:
+		printf("Token: %7s\n", "EOF");
+		break;
+	default:
+		printf("Token: ??? %u\n", tok->tokt);
+	}
+}
+
+int proc_token_iserror(const proc_token_t *tok) {
+	switch (tok->tokt) {
+	case PTOK_IDENT:
+	case PTOK_KEYWORD:
+	case PTOK_NUM:
+	case PTOK_STRING:
+	case PTOK_SYM:
+	case PTOK_PRAGMA:
+	case PTOK_EOF:
+		return 0;
+	case PTOK_INVALID:
+	default:
+		return 1;
+	}
+}
+
+int proc_token_isend(const proc_token_t *tok) {
+	switch (tok->tokt) {
+	case PTOK_IDENT:
+	case PTOK_KEYWORD:
+	case PTOK_NUM:
+	case PTOK_STRING:
+	case PTOK_SYM:
+	case PTOK_PRAGMA:
+		return 0;
+	case PTOK_INVALID:
+	case PTOK_EOF:
+	default:
+		return 1;
+	}
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wanalyzer-null-dereference"
+KHASH_MAP_IMPL_STR(str2kw, enum token_keyword_type_e)
+KHASH_MAP_IMPL_STR(type_map, type_t*)
+KHASH_MAP_IMPL_STR(struct_map, struct_t*)
+KHASH_MAP_IMPL_STR(const_map, num_constant_t)
+#pragma GCC diagnostic pop
+khash_t(str2kw) *str2kw;
+
+int init_str2kw(void) {
+	if (!(str2kw = kh_init(str2kw))) {
+		printf("Failed to create the string to keyword map (init)\n");
+		return 0;
+	}
+	for (enum token_keyword_type_e kw = 0; kw <= LAST_KEYWORD; ++kw) {
+		int iret;
+		khiter_t it = kh_put(str2kw, str2kw, kw2str[kw], &iret);
+		if (iret < 0) {
+			printf("Failed to create the string to keyword map (keyword %u)\n", kw);
+			kh_destroy(str2kw, str2kw);
+			return 0;
+		}
+		kh_val(str2kw, it) = kw;
+	}
+	return 1;
+}
+void del_str2kw(void) {
+	kh_destroy(str2kw, str2kw);
+}
+
+int num_constant_convert(string_t *str, num_constant_t *cst) {
+	if (string_len(str) == 0) return 0; // Should never happen
+#define contains(c) strchr(string_content(str), c)
+	if (contains('.')
+	     || (!(contains('X') || contains('x')) && (contains('E') || contains('e')))
+	     || ((contains('X') || contains('x')) && (contains('P') || contains('p')))) {
+#undef contains
+		int ok;
+		if ((string_end(str)[-1] == 'l') || (string_end(str)[-1] == 'L')) {
+			cst->typ = NCT_LDOUBLE;
+			char *endc;
+			cst->val.l = strtold(string_content(str), &endc);
+			ok = (endc == string_end(str) - 1);
+		} else if ((string_end(str)[-1] == 'f') || (string_end(str)[-1] == 'F')) {
+			cst->typ = NCT_FLOAT;
+			char *endc;
+			cst->val.f = strtof(string_content(str), &endc);
+			ok = (endc == string_end(str) - 1);
+		} else {
+			cst->typ = NCT_DOUBLE;
+			char *endc;
+			cst->val.d = strtod(string_content(str), &endc);
+			ok = (endc == string_end(str));
+		}
+		if (!ok) {
+			printf("Error: '%s' is not a valid number\n", string_content(str));
+			return 0;
+		} else if (errno == ERANGE) {
+			printf("Warning: floating point constant is too large\n");
+			return 1;
+		}
+		return 1;
+	} else {
+		uint64_t ret = 0;
+		unsigned base = (string_content(str)[0] == '0') ? (string_content(str)[1] == 'x') ? 16 : 8 : 10;
+		size_t startidx = (base == 16) ? 2 : 0;
+		size_t endidx = string_len(str);
+		int suffix_type = 0;
+#define SUFFIX_U 1
+#define SUFFIX_L 2
+#define SUFFIX_LL 4
+#define SUFFIX_SGN SUFFIX_U
+#define SUFFIX_SZ (SUFFIX_L | SUFFIX_LL)
+	remove_suffix:
+		if ((string_content(str)[endidx - 1] == 'l') || (string_content(str)[endidx - 1] == 'L')) {
+			if (suffix_type & SUFFIX_SZ) {
+				printf("Error: '%s' is not a valid number (invalid suffix)\n", string_content(str));
+				return 0;
+			}
+			if (endidx == 1) return 0; // Should never happen
+			if ((string_content(str)[endidx - 2] == 'l') || (string_content(str)[endidx - 2] == 'L')) {
+				if (endidx == 2) return 0; // Should never happen
+				if (string_content(str)[endidx - 2] != string_content(str)[endidx - 1]) {
+					printf("Error: '%s' is not a valid number (invalid suffix)\n", string_content(str));
+					return 0;
+				}
+				endidx -= 2;
+				suffix_type |= SUFFIX_LL;
+			} else {
+				endidx -= 1;
+				suffix_type |= SUFFIX_L;
+			}
+			goto remove_suffix;
+		}
+		if ((string_content(str)[endidx - 1] == 'u') || (string_content(str)[endidx - 1] == 'U')) {
+			if (suffix_type & SUFFIX_SGN) {
+				printf("Error: '%s' is not a valid number (invalid suffix)\n", string_content(str));
+				return 0;
+			}
+			endidx -= 1;
+			suffix_type |= SUFFIX_U;
+			goto remove_suffix;
+		}
+		// 0u has startidx=0 endidx=1
+		if (endidx <= startidx) {
+			printf("Error: '%s' is not a valid number\n", string_content(str));
+		}
+		for (size_t i = startidx; i < endidx; ++i) {
+			if ((string_content(str)[i] >= '0') && (string_content(str)[i] <= ((base == 8) ? '7' : '9'))) {
+				ret = base * ret + (unsigned)(string_content(str)[i] - '0');
+			} else if ((base == 16) && (string_content(str)[i] >= 'A') && (string_content(str)[i] <= 'F')) {
+				ret = base * ret + 10 + (unsigned)(string_content(str)[i] - 'A');
+			} else if ((base == 16) && (string_content(str)[i] >= 'a') && (string_content(str)[i] <= 'f')) {
+				ret = base * ret + 10 + (unsigned)(string_content(str)[i] - 'a');
+			} else {
+				printf("Error: '%s' is not a valid number\n", string_content(str));
+				return 0;
+			}
+		}
+		// If base == 10, keep the signness; in any case, try 32 bits if available else use 64 bits
+		cst->typ =
+			((suffix_type & SUFFIX_SGN) == SUFFIX_U) ?
+				((suffix_type & SUFFIX_SZ) == SUFFIX_L) ? LONG_IS_32BITS ? NCT_UINT32 : NCT_UINT64 :
+				((suffix_type & SUFFIX_SZ) == SUFFIX_LL) ? NCT_UINT64 :
+					NCT_UINT32 :
+				((suffix_type & SUFFIX_SZ) == SUFFIX_L) ? LONG_IS_32BITS ? NCT_INT32 : NCT_INT64 :
+				((suffix_type & SUFFIX_SZ) == SUFFIX_LL) ? NCT_INT64 :
+					NCT_INT32;
+		if (cst->typ == NCT_INT32) {
+			if (ret < 1ULL << 31) {
+				cst->val.i32 = (int32_t)ret;
+				return 1;
+			}
+			// Not in signed 32-bits = int => try long/long long for decimal, unsigned for hexadecimal/octal
+			if (base != 10) cst->typ = NCT_UINT32;
+			else cst->typ = NCT_INT64;
+		}
+		if (cst->typ == NCT_UINT32) {
+			if (ret < 1ULL << 32) {
+				cst->val.u32 = (uint32_t)ret;
+				return 1;
+			}
+			// Not in unsigned 32-bits = unsigned => try unsigned long/long long for decimal and unsigned, long/long long for signed hexadecimal/octal
+			if ((base != 10) && ((suffix_type & SUFFIX_SGN) != SUFFIX_U)) cst->typ = NCT_INT64;
+			else cst->typ = NCT_UINT64;
+		}
+		if (cst->typ == NCT_INT64) {
+			if (ret < 1ULL << 63) {
+				cst->val.i64 = (int64_t)ret;
+				return 1;
+			}
+			// Not in signed 64-bits = long/long long => fail for decimal, try unsigned long/long long for hexadecimal/octal
+			if (base != 10) cst->typ = NCT_UINT64;
+		}
+		if (cst->typ == NCT_UINT64) {
+			cst->val.u64 = ret;
+			return 1;
+		}
+		// The constant cannot be typed... (Or internal error)
+		return 0;
+	}
+}
+
+void expr_del(expr_t *e) {
+	switch (e->typ) {
+	case ETY_VAR:
+		string_del(e->val.var);
+		break;
+		
+	case ETY_CONST:
+		break;
+		
+	// case ETY_GENERIC:
+	// TODO
+		
+	case ETY_CALL:
+		expr_del(e->val.call.fun);
+		for (size_t i = 0; i < e->val.call.nargs; ++i) {
+			expr_del(e->val.call.args[i]);
+		}
+		if (e->val.call.args) free(e->val.call.args);
+		break;
+		
+	case ETY_ACCESS:
+	case ETY_PTRACCESS:
+		expr_del(e->val.access.val);
+		string_del(e->val.access.member);
+		break;
+		
+	case ETY_UNARY:
+		expr_del(e->val.unary.e);
+		break;
+		
+	case ETY_BINARY:
+		expr_del(e->val.binary.e1);
+		expr_del(e->val.binary.e2);
+		break;
+		
+	case ETY_TERNARY:
+		expr_del(e->val.ternary.e1);
+		expr_del(e->val.ternary.e2);
+		expr_del(e->val.ternary.e3);
+		break;
+		
+	// case ETY_INIT_LIST:
+	// TODO
+		
+	case ETY_CAST:
+		type_del(e->val.cast.typ);
+		expr_del(e->val.cast.e);
+		break;
+	}
+	free(e);
+}
+
+void struct_del_weak(struct_t *st) {
+	if (--st->nrefs) return;
+	if (!st->tag) struct_del(st);
+}
+void type_del(type_t *typ) {
+	if (--typ->nrefs) return;
+	switch (typ->typ) {
+	case TYPE_BUILTIN:
+		break;
+	case TYPE_ARRAY:
+		type_del(typ->val.array.typ);
+		break;
+	case TYPE_STRUCT_UNION:
+		struct_del_weak(typ->val.st);
+		break;
+	case TYPE_ENUM:
+	case TYPE_PTR:
+		type_del(typ->val.typ);
+		break;
+	case TYPE_FUNCTION:
+		type_del(typ->val.fun.ret);
+		if (typ->val.fun.nargs != (size_t)-1) {
+			for (size_t i = 0; i < typ->val.fun.nargs; ++i) {
+				type_del(typ->val.fun.args[i]);
+			}
+		}
+		if (typ->val.fun.args) free(typ->val.fun.args);
+		break;
+	}
+	free(typ);
+}
+void type_map_del(khash_t(type_map) *map) {
+	kh_cstr_t str;
+	type_t **it;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+	kh_foreach_key_value_ref(map, str, it, free((void*)str); type_del(*it))
+#pragma GCC diagnostic pop
+	kh_destroy(type_map, map);
+}
+void type_set_del(khash_t(type_set) *set) {
+	type_t *it;
+	kh_foreach_key(set, it, type_del(it))
+	kh_destroy(type_set, set);
+}
+void const_map_del(khash_t(const_map) *map) {
+	kh_cstr_t str;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+	kh_foreach_key(map, str, free((void*)str))
+#pragma GCC diagnostic pop
+	kh_destroy(const_map, map);
+}
+
+void st_member_del(st_member_t *member) {
+	if (member->name) string_del(member->name);
+	type_del(member->typ);
+}
+
+void struct_del(struct_t *st) {
+	if (st->tag) {
+		string_del(st->tag);
+		st->tag = NULL;
+	}
+	if (st->is_defined) {
+		for (size_t i = 0; i < st->nmembers; ++i) {
+			st_member_del(&st->members[i]);
+		}
+		free(st->members);
+		st->is_defined = 0;
+	}
+	if (!st->nrefs) free(st);
+}
+void struct_map_del(khash_t(struct_map) *map) {
+	// The keys are the tag in the struct_t
+	struct_t **it;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+	kh_foreach_value_ref(map, it, struct_del(*it))
+#pragma GCC diagnostic pop
+	kh_destroy(struct_map, map);
+}
+
+type_t *type_new(void) {
+	type_t *ret = malloc(sizeof *ret);
+	if (!ret) {
+		printf("Failed to create a new type\n");
+		return NULL;
+	}
+	ret->szinfo.align = ret->szinfo.size = 0;
+	ret->is_atomic = ret->is_const = ret->is_restrict = ret->is_volatile = ret->is_incomplete = ret->is_validated = ret->_internal_use = 0;
+	ret->nrefs = 1;
+	ret->typ = TYPE_BUILTIN;
+	ret->val.builtin = BTT_INT;
+	return ret;
+}
+type_t *type_new_ptr(type_t *target) {
+	type_t *ret = malloc(sizeof *ret);
+	if (!ret) {
+		printf("Failed to create a new pointer type\n");
+		return NULL;
+	}
+	ret->szinfo.align = ret->szinfo.size = 0;
+	ret->is_atomic = ret->is_const = ret->is_restrict = ret->is_volatile = ret->is_incomplete = ret->is_validated = ret->_internal_use = 0;
+	ret->nrefs = 1;
+	ret->typ = TYPE_PTR;
+	ret->val.typ = target;
+	return ret;
+}
+// The following functions do not work for functions (val.args needs to be duplicated)
+/* type_t *type_do_copy(type_t *ref) {
+	type_t *ret = type_do_copy_nodec(ref);
+	if (!ret) return NULL;
+	type_del(ref);
+	return ret;
+}
+type_t *type_do_copy_nodec(const type_t *ref) {
+	type_t *ret = malloc(sizeof *ret);
+	if (!ret) {
+		printf("Failed to duplicate type\n");
+		return NULL;
+	}
+	memcpy(ret, ref, sizeof *ret);
+	switch (ref->typ) {
+	case TYPE_BUILTIN:
+		break;
+	case TYPE_ARRAY:
+		++ref->val.array.typ->nrefs;
+		break;
+	case TYPE_STRUCT_UNION:
+		++ret->val.st->nrefs;
+		break;
+	case TYPE_ENUM:
+	case TYPE_PTR:
+		++ref->val.typ->nrefs;
+		break;
+	case TYPE_FUNCTION:
+		++ref->val.fun.ret->nrefs;
+		if (ref->val.fun.nargs != (size_t)-1) {
+			for (size_t i = 0; i < ref->val.fun.nargs; ++i) {
+				++ref->val.fun.args[i]->nrefs;
+			}
+		}
+		break;
+	}
+	ret->nrefs = 1;
+	return ret;
+}
+type_t *type_maybe_copy(type_t *ref) {
+	if (ref->nrefs == 1) return ref;
+	else return type_do_copy(ref);
+} */
+int type_copy_into(type_t *dest, const type_t *ref) {
+	size_t nrefs = dest->nrefs;
+	_Bool is_atomic = dest->is_atomic;
+	_Bool is_const = dest->is_const;
+	_Bool is_restrict = dest->is_restrict;
+	_Bool is_volatile = dest->is_volatile;
+	memcpy(dest, ref, sizeof *dest);
+	switch (ref->typ) {
+	case TYPE_BUILTIN:
+		break;
+	case TYPE_ARRAY:
+		++ref->val.array.typ->nrefs;
+		break;
+	case TYPE_STRUCT_UNION:
+		++dest->val.st->nrefs;
+		break;
+	case TYPE_ENUM:
+	case TYPE_PTR:
+		++ref->val.typ->nrefs;
+		break;
+	case TYPE_FUNCTION:
+		++ref->val.fun.ret->nrefs;
+		if (ref->val.fun.nargs != (size_t)-1) {
+			if (!ref->val.fun.args) {
+				dest->typ = TYPE_BUILTIN;
+				return 0;
+			}
+			type_t **args = malloc(sizeof *args * ref->val.fun.nargs);
+			if (!args) {
+				printf("Error: failed to allocate new argument array\n");
+				return 0;
+			}
+			for (size_t i = 0; i < ref->val.fun.nargs; ++i) {
+				++ref->val.fun.args[i]->nrefs;
+				args[i] = ref->val.fun.args[i];
+			}
+			dest->val.fun.args = args;
+		}
+		break;
+	}
+	dest->is_atomic |= is_atomic;
+	dest->is_const |= is_const;
+	dest->is_restrict |= is_restrict;
+	dest->is_volatile |= is_volatile;
+	dest->nrefs = nrefs;
+	return 1;
+}
+
+struct_t *struct_new(int is_struct, string_t *tag) {
+	struct_t *ret = malloc(sizeof *ret);
+	if (!ret) {
+		printf("Failed to create a new struct\n");
+		return NULL;
+	}
+	ret->is_struct = is_struct;
+	ret->tag = tag;
+	ret->is_defined = 0;
+	ret->nrefs = 1;
+	ret->explicit_simple = 0;
+	return ret;
+}
+
+_Static_assert(sizeof(type_t*) == sizeof(khint64_t), "Not a 64-bits machine");
+khint_t type_t_hash(type_t *typ) {
+	switch (typ->typ) {
+	case TYPE_BUILTIN: return kh_int_hash_func(typ->val.builtin);
+	case TYPE_ARRAY: return kh_int_hash_func((typ->val.array.array_sz << 12) + type_t_hash(typ->val.array.typ));
+	case TYPE_STRUCT_UNION:
+		if (typ->val.st->tag) {
+			return kh_str_hash_func(string_content(typ->val.st->tag));
+		} else {
+			khint64_t acc = (typ->val.st->nmembers << 5) + 1;
+			for (size_t i = 0; i < typ->val.st->nmembers; ++i) {
+				acc += (khint64_t)type_t_hash(typ->val.st->members[i].typ) << ((1ull << (i % 64)) % 61);
+			}
+			return kh_int64_hash_func(acc);
+		}
+	case TYPE_ENUM:
+	case TYPE_PTR:
+		return kh_int_hash_func(type_t_hash(typ->val.typ));
+	case TYPE_FUNCTION: {
+		khint64_t acc = type_t_hash(typ->val.fun.ret) + typ->val.fun.nargs * 73 + (size_t)typ->val.fun.has_varargs * 77;
+		if (typ->val.fun.nargs != (size_t)-1) {
+			for (size_t i = 0; i < typ->val.fun.nargs; ++i) {
+				acc += (khint64_t)type_t_hash(typ->val.fun.args[i]) << ((3ull << (i % 64)) % 53);
+			}
+		}
+		return kh_int64_hash_func(acc); }
+	default: return 0;
+	}
+}
+int type_t_equal(type_t *typ1, type_t *typ2) {
+	if (typ1->typ != typ2->typ) return 0;
+	if ((typ1->is_atomic != typ2->is_atomic) || (typ1->is_const != typ2->is_const)
+	 || (typ1->is_restrict != typ2->is_restrict) || (typ1->is_volatile != typ2->is_volatile)) {
+		return 0;
+	}
+	switch (typ1->typ) {
+	case TYPE_BUILTIN: return typ1->val.builtin == typ2->val.builtin;
+	case TYPE_ARRAY: return (typ1->val.array.array_sz == typ2->val.array.array_sz) && type_t_equal(typ1->val.array.typ, typ2->val.array.typ);
+	case TYPE_STRUCT_UNION:
+		// This will not do an infinite recursion since only unnamed struct/unions will compare their members
+		if (!typ1->val.st->tag != !typ2->val.st->tag) return 0;
+		if (typ1->val.st->tag) return !strcmp(string_content(typ1->val.st->tag), string_content(typ2->val.st->tag));
+		if (!typ1->val.st->is_defined || !typ2->val.st->is_defined) {
+			printf("Warning: incomplete anonymous structure/union\n");
+			return 0;
+		}
+		if (typ1->val.st->nmembers != typ2->val.st->nmembers) return 0;
+		for (size_t i = 0; i < typ1->val.st->nmembers; ++i) {
+			if (!!typ1->val.st->members[i].name != !!typ2->val.st->members[i].name) return 0;
+			if (typ1->val.st->members[i].name
+			     && strcmp(string_content(typ1->val.st->members[i].name), string_content(typ2->val.st->members[i].name)))
+				return 0;
+			if (!type_t_equal(typ1->val.st->members[i].typ, typ2->val.st->members[i].typ)) return 0;
+		}
+		return 1;
+	case TYPE_ENUM:
+	case TYPE_PTR:
+		return type_t_equal(typ1->val.typ, typ2->val.typ);
+	case TYPE_FUNCTION:
+		if (typ1->val.fun.nargs != typ2->val.fun.nargs) return 0;
+		if (typ1->val.fun.has_varargs != typ2->val.fun.has_varargs) return 0;
+		if (!type_t_equal(typ1->val.fun.ret, typ2->val.fun.ret)) return 0;
+		if (typ1->val.fun.nargs != (size_t)-1) {
+			for (size_t i = 0; i < typ1->val.fun.nargs; ++i) {
+				if (!type_t_equal(typ1->val.fun.args[i], typ2->val.fun.args[i])) return 0;
+			}
+		}
+		return 1;
+	default: return 0;
+	}
+}
+__KHASH_IMPL(type_set, , type_t*, char, 0, type_t_hash, type_t_equal)
+
+type_t *type_try_merge(type_t *typ, khash_t(type_set) *set) {
+	int iret;
+	khiter_t it = kh_put(type_set, set, typ, &iret);
+	if (iret < 0) {
+		printf("Error: failed to add type to type_set\n");
+		return NULL;
+	} else if (iret == 0) {
+		if (typ == kh_key(set, it)) return typ;
+		kh_key(set, it)->is_incomplete &= typ->is_incomplete; // In case we have a recursive structure
+		type_del(typ);
+		++kh_key(set, it)->nrefs;
+		return kh_key(set, it);
+	} else {
+		++typ->nrefs;
+	}
+	type_t *typ2;
+	switch (typ->typ) {
+	case TYPE_BUILTIN:
+		return typ;
+	case TYPE_ARRAY:
+		typ2 = type_try_merge(typ->val.array.typ, set);
+		if (typ2) typ->val.array.typ = typ2;
+		else return NULL;
+		return typ;
+	case TYPE_STRUCT_UNION:
+		if (typ->val.st->is_defined) {
+			for (size_t i = 0; i < typ->val.st->nmembers; ++i) {
+				typ2 = type_try_merge(typ->val.st->members[i].typ, set);
+				if (typ2) typ->val.st->members[i].typ = typ2;
+				else return NULL;
+			}
+		}
+		return typ;
+	case TYPE_ENUM:
+	case TYPE_PTR:
+		typ2 = type_try_merge(typ->val.typ, set);
+		if (typ2) typ->val.typ = typ2;
+		else return NULL;
+		return typ;
+	case TYPE_FUNCTION:
+		typ2 = type_try_merge(typ->val.fun.ret, set);
+		if (typ2) typ->val.fun.ret = typ2;
+		else return NULL;
+		
+		if (typ->val.fun.nargs != (size_t)-1) {
+			for (size_t i = 0; i < typ->val.fun.nargs; ++i) {
+				typ2 = type_try_merge(typ->val.fun.args[i], set);
+				if (typ2) typ->val.fun.args[i] = typ2;
+				else return NULL;
+			}
+		}
+		return typ;
+		
+	default: return NULL;
+	}
+}
+
+const char *builtin2str[LAST_BUILTIN + 1] = {
+	[BTT_VOID] = "void",
+	[BTT_BOOL] = "_Bool",
+	[BTT_CHAR] = "char",
+	[BTT_SCHAR] = "signed char",
+	[BTT_UCHAR] = "unsigned char",
+	[BTT_SHORT] = "short",
+	[BTT_SSHORT] = "signed short",
+	[BTT_USHORT] = "unsigned short",
+	[BTT_INT] = "int",
+	[BTT_SINT] = "signed",
+	[BTT_UINT] = "unsigned",
+	[BTT_LONG] = "long",
+	[BTT_SLONG] = "signed long",
+	[BTT_ULONG] = "unsigned long",
+	[BTT_LONGLONG] = "long long",
+	[BTT_SLONGLONG] = "signed long long",
+	[BTT_ULONGLONG] = "unsigned long long",
+	[BTT_INT128] = "__int128",
+	[BTT_SINT128] = "signed __int128",
+	[BTT_UINT128] = "unsigned __int128",
+	[BTT_S8] = "int8_t",
+	[BTT_U8] = "uint8_t",
+	[BTT_S16] = "int16_t",
+	[BTT_U16] = "uint16_t",
+	[BTT_S32] = "int32_t",
+	[BTT_U32] = "uint32_t",
+	[BTT_S64] = "int64_t",
+	[BTT_U64] = "uint64_t",
+	[BTT_FLOAT] = "float",
+	[BTT_CFLOAT] = "float _Complex",
+	[BTT_IFLOAT] = "float _Imaginary",
+	[BTT_DOUBLE] = "double",
+	[BTT_CDOUBLE] = "double _Complex",
+	[BTT_IDOUBLE] = "double _Imaginary",
+	[BTT_LONGDOUBLE] = "long double",
+	[BTT_CLONGDOUBLE] = "long double _Complex",
+	[BTT_ILONGDOUBLE] = "long double _Imaginary",
+	[BTT_VA_LIST] = "__builtin_va_list",
+};
+void type_print(type_t *typ) {
+	if (typ->_internal_use) {
+		printf("...%p", typ);
+		return;
+	}
+	typ->_internal_use = 1;
+	printf("<" DISP_ADDR_FMT "n_uses=%zu> ", DISP_ADDR_ARG(typ) typ->nrefs);
+	if (!typ->is_validated) printf("!<not validated> ");
+	if (typ->is_incomplete) printf("<incomplete> ");
+	if (typ->is_validated && !typ->is_incomplete) printf("<size=%zu align=%zu> ", typ->szinfo.size, typ->szinfo.align);
+	if (typ->is_const) printf("const ");
+	if (typ->is_restrict) printf("restrict ");
+	if (typ->is_volatile) printf("volatile ");
+	if (typ->is_atomic) printf("_Atomic ");
+	switch (typ->typ) {
+	case TYPE_BUILTIN:
+		printf("<builtin %s (%u)>", builtin2str[typ->val.builtin], typ->val.builtin);
+		break;
+	case TYPE_ARRAY:
+		if (typ->val.array.array_sz == (size_t)-1)
+			printf("[] ");
+		else printf("[%zu] ", typ->val.array.array_sz);
+		type_print(typ->val.array.typ);
+		break;
+	case TYPE_STRUCT_UNION:
+		struct_print(typ->val.st);
+		break;
+	case TYPE_ENUM:
+		printf("<enum> ");
+		type_print(typ->val.typ);
+		break;
+	case TYPE_PTR:
+		printf("*");
+		type_print(typ->val.typ);
+		break;
+	case TYPE_FUNCTION:
+		printf("<function type, ret=");
+		type_print(typ->val.fun.ret);
+		if (typ->val.fun.nargs == (size_t)-1) {
+			printf(", no argument spec>");
+		} else {
+			printf(", args=(");
+			for (size_t i = 0; i < typ->val.fun.nargs; ++i) {
+				if (i) printf("), (");
+				type_print(typ->val.fun.args[i]);
+			}
+			if (typ->val.fun.has_varargs && typ->val.fun.nargs) printf(", ...)>");
+			else if (typ->val.fun.has_varargs && !typ->val.fun.nargs) printf("...)>");
+			else printf(")>");
+		}
+		break;
+	}
+	typ->_internal_use = 0;
+}
+void struct_print(const struct_t *st) {
+	printf("<" DISP_ADDR_FMT "n_uses=%zu> ", DISP_ADDR_ARG(st) st->nrefs);
+	if (st->explicit_simple) {
+		printf("<explicitly simple> ");
+	}
+	if (st->is_defined) {
+		printf(
+			"%s %s <with %zu members%s> { ",
+			st->is_struct ? "struct" : "union",
+			st->tag ? string_content(st->tag) : "<no tag>",
+			st->nmembers,
+			st->has_incomplete ? ", with incomplete" : "");
+		for (size_t i = 0; i < st->nmembers; ++i) {
+			if (i) printf(", ");
+			type_print(st->members[i].typ);
+			printf(" %s", st->members[i].name ? string_content(st->members[i].name) : "<no name>");
+			if (st->members[i].is_bitfield) {
+				printf(" : %zu", st->members[i].bitfield_width);
+			}
+		}
+		printf(" }");
+	} else {
+		printf("%s %s <undefined>", st->is_struct ? "struct" : "union", st->tag ? string_content(st->tag) : "<no tag>");
+	}
+}
+
+// TODO: per-arch array
+size_t sizeof_btt[LAST_BUILTIN + 1] = {
+	[BTT_VOID] = 0,
+	[BTT_BOOL] = 1,
+	[BTT_CHAR] = 1,
+	[BTT_SCHAR] = 1,
+	[BTT_UCHAR] = 1,
+	[BTT_SHORT] = 2,
+	[BTT_SSHORT] = 2,
+	[BTT_USHORT] = 2,
+	[BTT_INT] = 4,
+	[BTT_SINT] = 4,
+	[BTT_UINT] = 4,
+	[BTT_LONG] = LONG_IS_32BITS ? 4 : 8,
+	[BTT_SLONG] = LONG_IS_32BITS ? 4 : 8,
+	[BTT_ULONG] = LONG_IS_32BITS ? 4 : 8,
+	[BTT_LONGLONG] = 8,
+	[BTT_SLONGLONG] = 8,
+	[BTT_ULONGLONG] = 8,
+	[BTT_INT128] = 16,
+	[BTT_SINT128] = 16,
+	[BTT_UINT128] = 16,
+	[BTT_S8] = 1,
+	[BTT_U8] = 1,
+	[BTT_S16] = 2,
+	[BTT_U16] = 2,
+	[BTT_S32] = 4,
+	[BTT_U32] = 4,
+	[BTT_S64] = 8,
+	[BTT_U64] = 8,
+	[BTT_FLOAT] = 4,
+	[BTT_CFLOAT] = 8,
+	[BTT_IFLOAT] = 4,
+	[BTT_DOUBLE] = 8,
+	[BTT_CDOUBLE] = 16,
+	[BTT_IDOUBLE] = 8,
+	[BTT_LONGDOUBLE] = 16,
+	[BTT_CLONGDOUBLE] = 32,
+	[BTT_ILONGDOUBLE] = 16,
+	[BTT_VA_LIST] = LONG_IS_32BITS ? 0 : 24, // TODO
+};
+// The following assumes sizeof(unsigned long) == sizeof(void*)
+file_t *file_new(void) {
+	file_t *ret = malloc(sizeof *ret);
+	if (!ret) {
+		printf("Failed to create a new translation unit structure (init)\n");
+		return NULL;
+	}
+	if (!(ret->struct_map = kh_init(struct_map))) {
+		printf("Failed to create a new translation unit structure (structure map)\n");
+		free(ret);
+		return NULL;
+	}
+	if (!(ret->type_map = kh_init(type_map))) {
+		printf("Failed to create a new translation unit structure (type map)\n");
+		kh_destroy(struct_map, ret->struct_map);
+		free(ret);
+		return NULL;
+	}
+	if (!(ret->enum_map = kh_init(type_map))) {
+		printf("Failed to create a new translation unit structure (enumeration map)\n");
+		kh_destroy(struct_map, ret->struct_map);
+		kh_destroy(type_map, ret->type_map);
+		free(ret);
+		return NULL;
+	}
+	if (!(ret->decl_map = kh_init(type_map))) {
+		printf("Failed to create a new translation unit structure (declaration map)\n");
+		kh_destroy(struct_map, ret->struct_map);
+		kh_destroy(type_map, ret->type_map);
+		kh_destroy(type_map, ret->enum_map);
+		free(ret);
+		return NULL;
+	}
+	if (!(ret->type_set = kh_init(type_set))) {
+		printf("Failed to create a new translation unit structure (type set)\n");
+		kh_destroy(struct_map, ret->struct_map);
+		kh_destroy(type_map, ret->type_map);
+		kh_destroy(type_map, ret->enum_map);
+		kh_destroy(type_map, ret->decl_map);
+		free(ret);
+		return NULL;
+	}
+	if (!(ret->const_map = kh_init(const_map))) {
+		printf("Failed to create a new translation unit structure (const map)\n");
+		kh_destroy(struct_map, ret->struct_map);
+		kh_destroy(type_map, ret->type_map);
+		kh_destroy(type_map, ret->enum_map);
+		kh_destroy(type_map, ret->decl_map);
+		kh_destroy(type_set, ret->type_set);
+		free(ret);
+		return NULL;
+	}
+	
+	// Now fill in the builtin types
+	int iret;
+	for (enum type_builtin_e i = 0; i < LAST_BUILTIN + 1; ++i) {
+		type_t *t = malloc(sizeof *t);
+		if (!t) {
+			printf("Failed to create a new translation unit structure (builtin type)\n");
+			for (; i--;) {
+				free(ret->builtins[i]);
+			}
+			kh_destroy(struct_map, ret->struct_map);
+			kh_destroy(type_map, ret->type_map);
+			kh_destroy(type_map, ret->enum_map);
+			kh_destroy(type_map, ret->decl_map);
+			kh_destroy(type_set, ret->type_set);
+			kh_destroy(const_map, ret->const_map);
+			free(ret);
+			return NULL;
+		}
+		t->is_atomic = t->is_const = t->is_restrict = t->is_volatile = t->_internal_use = 0;
+		t->is_incomplete = (i == BTT_VOID);
+		t->is_validated = 1;
+		t->nrefs = 2;
+		t->typ = TYPE_BUILTIN;
+		t->val.builtin = i;
+		t->szinfo.align = t->szinfo.size = sizeof_btt[i];
+		ret->builtins[i] = t;
+		kh_put(type_set, ret->type_set, t, &iret);
+		if (iret < 0) {
+			printf("Failed to create a new translation unit structure (failed to add intrinsic type to type_set)\n");
+			kh_destroy(struct_map, ret->struct_map);
+			kh_destroy(type_map, ret->type_map);
+			kh_destroy(type_map, ret->enum_map);
+			kh_destroy(type_map, ret->decl_map);
+			kh_destroy(type_set, ret->type_set);
+			kh_destroy(const_map, ret->const_map);
+			free(ret);
+			return NULL;
+		} else if (iret == 0) {
+			printf("Failed to create a new translation unit structure (duplicate intrinsic type in type_set)\n");
+			for (++i; i--;) {
+				free(ret->builtins[i]);
+			}
+			kh_destroy(struct_map, ret->struct_map);
+			kh_destroy(type_map, ret->type_map);
+			kh_destroy(type_map, ret->enum_map);
+			kh_destroy(type_map, ret->decl_map);
+			kh_destroy(type_set, ret->type_set);
+			kh_destroy(const_map, ret->const_map);
+			free(ret);
+			return NULL;
+		}
+	}
+	// ret is valid
+	
+	// Add __builtin_va_list as a typedef
+	char *sdup = strdup("__builtin_va_list");
+	if (!sdup) {
+		printf("Failed to create a new translation unit structure (va_list name)\n");
+		file_del(ret);
+		return NULL;
+	}
+	khiter_t it = kh_put(type_map, ret->type_map, sdup, &iret);
+	if (iret < 0) {
+		printf("Failed to create a new translation unit structure (add va_list typedef)\n");
+		free(sdup);
+		file_del(ret);
+		return NULL;
+	} else if (iret == 0) {
+		printf("Failed to create a new translation unit structure (__builtin_va_list is already a typedef)\n");
+		file_del(ret);
+		return NULL;
+	}
+	kh_val(ret->type_map, it) = ret->builtins[BTT_VA_LIST];
+	++ret->builtins[BTT_VA_LIST]->nrefs;
+	
+	return ret;
+}
+void file_del(file_t *f) {
+	struct_map_del(f->struct_map);
+	type_map_del(f->type_map);
+	type_map_del(f->enum_map);
+	type_map_del(f->decl_map);
+	type_set_del(f->type_set);
+	const_map_del(f->const_map);
+	for (enum type_builtin_e i = 0; i < LAST_BUILTIN + 1; ++i) {
+		type_del(f->builtins[i]);
+	}
+	free(f);
+}
diff --git a/wrapperhelper/src/lang.h b/wrapperhelper/src/lang.h
new file mode 100644
index 00000000..568ab855
--- /dev/null
+++ b/wrapperhelper/src/lang.h
@@ -0,0 +1,439 @@
+#pragma once
+
+#ifndef LANG_H
+#define LANG_H
+
+#include "cstring.h"
+#include "khash.h"
+#include "vector.h"
+
+#define LONG_IS_32BITS 0
+
+enum token_sym_type_e {
+	SYM_LBRACKET,
+	SYM_RBRACKET,
+	SYM_LSQBRACKET,
+	SYM_RSQBRACKET,
+	SYM_LPAREN,
+	SYM_RPAREN,
+	SYM_HASH,
+	SYM_HASHHASH,
+	SYM_SEMICOLON,
+	SYM_COLON,
+	SYM_COLONCOLON,
+	SYM_VARIADIC,
+	SYM_QUESTION,
+	SYM_DOT,
+	SYM_DASHGT,
+	SYM_TILDE,
+	SYM_EXCL,
+	SYM_PLUS,
+	SYM_DASH,
+	SYM_STAR,
+	SYM_SLASH,
+	SYM_PERCENT,
+	SYM_HAT,
+	SYM_AMP,
+	SYM_PIPE,
+	SYM_EQ,
+	SYM_PLUSEQ,
+	SYM_DASHEQ,
+	SYM_STAREQ,
+	SYM_SLASHEQ,
+	SYM_PERCENTEQ,
+	SYM_HATEQ,
+	SYM_AMPEQ,
+	SYM_PIPEEQ,
+	SYM_EQEQ,
+	SYM_EXCLEQ,
+	SYM_LT,
+	SYM_GT,
+	SYM_LTEQ,
+	SYM_GTEQ,
+	SYM_AMPAMP,
+	SYM_PIPEPIPE,
+	SYM_LTLT,
+	SYM_GTGT,
+	SYM_LTLTEQ,
+	SYM_GTGTEQ,
+	SYM_PLUSPLUS,
+	SYM_DASHDASH,
+	SYM_COMMA,
+};
+#define LAST_SYM SYM_COMMA
+
+typedef struct preproc_token_s {
+	enum preproc_token_e {
+		PPTOK_INVALID = 0,
+		PPTOK_IDENT,       // Expandable ident
+		PPTOK_IDENT_UNEXP, // Unexpandable ident
+		PPTOK_NUM,
+		PPTOK_STRING,
+		PPTOK_INCL,
+		PPTOK_SYM,
+		PPTOK_NEWLINE,
+		PPTOK_BLANK,
+		PPTOK_START_LINE_COMMENT,
+		PPTOK_EOF
+	} tokt;
+	union {
+		string_t *str;
+		struct {
+			string_t *sstr; // The string literal content
+			int sisstr; // 0 for '' or <>, 1 for ""
+		};
+		char c;
+		enum token_sym_type_e sym;
+	} tokv;
+} preproc_token_t;
+VECTOR_DECLARE(preproc, preproc_token_t)
+void preproc_token_del(preproc_token_t *tok);
+
+enum token_keyword_type_e {
+	KW_ALIGNAS = 0,
+	KW_ALIGNOF,
+	KW_ATOMIC,
+	KW_AUTO,
+	KW_BOOL,
+	KW_BREAK,
+	KW_CASE,
+	KW_CHAR,
+	KW_COMPLEX,
+	KW_CONST,
+	KW_CONTINUE,
+	KW_DEFAULT,
+	KW_DO,
+	KW_DOUBLE,
+	KW_ELSE,
+	KW_ENUM,
+	KW_EXTERN,
+	KW_FLOAT,
+	KW_FOR,
+	KW_GENERIC,
+	KW_GOTO,
+	KW_IF,
+	KW_IMAGINARY,
+	KW_INLINE,
+	KW_INT,
+	KW_INT128,
+	KW_LONG,
+	KW_NORETURN,
+	KW_REGISTER,
+	KW_RESTRICT,
+	KW_RETURN,
+	KW_SHORT,
+	KW_SIGNED,
+	KW_SIZEOF,
+	KW_STATIC,
+	KW_STATIC_ASSERT,
+	KW_STRUCT,
+	KW_SWITCH,
+	KW_THREAD_LOCAL,
+	KW_TYPEDEF,
+	KW_UNION,
+	KW_UNSIGNED,
+	KW_VOID,
+	KW_VOLATILE,
+	KW_WHILE,
+};
+#define LAST_KEYWORD KW_WHILE
+
+typedef struct proc_token_s {
+	enum proc_token_e {
+		PTOK_INVALID = 0,
+		PTOK_IDENT,
+		PTOK_KEYWORD,
+		PTOK_NUM,
+		PTOK_STRING,
+		PTOK_SYM,
+		PTOK_PRAGMA,
+		PTOK_EOF
+	} tokt;
+	union proc_token_val_u {
+		string_t *str;
+		struct {
+			string_t *sstr; // The string literal content
+			int sisstr; // 0 for '' or <>, 1 for ""
+		};
+		char c;
+		enum token_sym_type_e sym;
+		enum token_keyword_type_e kw;
+		struct {
+			enum proc_pragma_e {
+				PRAGMA_ALLOW_INTS,
+				PRAGMA_MARK_SIMPLE,
+			} typ;
+			string_t *val;
+		} pragma;
+	} tokv;
+} proc_token_t;
+VECTOR_DECLARE(proc, proc_token_t)
+void proc_token_del(proc_token_t *tok);
+
+typedef struct num_constant_s {
+	enum num_constant_e {
+		NCT_FLOAT,
+		NCT_DOUBLE,
+		NCT_LDOUBLE,
+		NCT_INT32,
+		NCT_UINT32,
+		NCT_INT64,
+		NCT_UINT64,
+	} typ;
+	union {
+		float f;
+		double d;
+		long double l;
+		int32_t i32;
+		uint32_t u32;
+		int64_t i64;
+		uint64_t u64;
+	} val;
+} num_constant_t;
+int num_constant_convert(string_t *str, num_constant_t *cst);
+KHASH_MAP_DECLARE_STR(const_map, num_constant_t)
+
+typedef struct expr_s {
+	enum expr_type_e {
+		ETY_VAR,
+		ETY_CONST,
+		// ETY_GENERIC,
+		ETY_CALL,
+		ETY_ACCESS,
+		ETY_PTRACCESS, // Convertible to DEREF + ACCESS
+		ETY_UNARY,
+		ETY_BINARY,
+		ETY_TERNARY,
+		// ETY_INIT_LIST,
+		ETY_CAST,
+	} typ;
+	union {
+		string_t *var;
+		num_constant_t cst;
+		// TODO: _Generic
+		struct {
+			struct expr_s *fun, **args;
+			size_t nargs;
+		} call;
+		struct {
+			struct expr_s *val;
+			string_t *member;
+		} access;
+		struct {
+			enum unary_op_e {
+				UOT_POSTINCR,
+				UOT_POSTDECR,
+				UOT_PREINCR,
+				UOT_PREDECR,
+				UOT_REF,
+				UOT_POS,
+				UOT_NEG,
+				UOT_DEREF,
+				UOT_ANOT, // Arithmetic not, ie '~'
+				UOT_BNOT, // Boolean not, ie '!'
+			} typ;
+			struct expr_s *e;
+		} unary;
+		struct {
+			enum binary_op_e {
+				BOT_ADD,
+				BOT_SUB,
+				BOT_MUL,
+				BOT_DIV,
+				BOT_MOD,
+				BOT_LSH,
+				BOT_RSH,
+				BOT_LT,
+				BOT_GT,
+				BOT_LE,
+				BOT_GE,
+				BOT_EQ,
+				BOT_NE,
+				BOT_AAND,
+				BOT_AXOR,
+				BOT_AOR,
+				BOT_BAND,
+				BOT_BOR,
+				BOT_ASSGN_EQ,
+				BOT_ASSGN_ADD,
+				BOT_ASSGN_SUB,
+				BOT_ASSGN_MUL,
+				BOT_ASSGN_DIV,
+				BOT_ASSGN_MOD,
+				BOT_ASSGN_LSH,
+				BOT_ASSGN_RSH,
+				BOT_ASSGN_AAND,
+				BOT_ASSGN_AXOR,
+				BOT_ASSGN_AOR,
+				BOT_COMMA,
+				
+				BOT_ARRAY, // Convertible to DEREF + ADD
+			} typ;
+			struct expr_s *e1, *e2;
+		} binary;
+		struct {
+			enum ternary_op_e {
+				TOT_COND,
+			} typ;
+			struct expr_s *e1, *e2, *e3;
+		} ternary;
+		// TODO: (type){init}
+		struct {
+			struct type_s *typ;
+			struct expr_s *e;
+		} cast;
+	} val;
+} expr_t;
+void expr_del(expr_t *e);
+
+typedef struct size_info_s {
+	size_t size, align;
+} size_info_t;
+
+typedef struct type_s {
+	struct {
+		unsigned is_atomic : 1;
+		unsigned is_const : 1;
+		unsigned is_restrict : 1;
+		unsigned is_volatile : 1;
+		unsigned is_incomplete : 1; // \ The type needs to be complete and
+		unsigned is_validated : 1;  // / validated for the size_info to be populated
+		unsigned _internal_use : 1;
+	};
+	size_t nrefs;
+	enum type_type_e {
+		TYPE_BUILTIN,      // builtin
+		TYPE_ARRAY,        // array
+		TYPE_STRUCT_UNION, // st
+		TYPE_ENUM,         // typ which points to TYPE_BUILTIN
+		TYPE_PTR,          // typ
+		TYPE_FUNCTION,     // fun
+	} typ;
+	union {
+		enum type_builtin_e {
+			BTT_VOID,
+			BTT_BOOL,
+			BTT_CHAR,
+			BTT_SCHAR,
+			BTT_UCHAR,
+			BTT_SHORT,
+			BTT_SSHORT,
+			BTT_USHORT,
+			BTT_INT,
+			BTT_SINT,
+			BTT_UINT,
+			BTT_LONG,
+			BTT_SLONG,
+			BTT_ULONG,
+			BTT_LONGLONG,
+			BTT_SLONGLONG,
+			BTT_ULONGLONG,
+			BTT_INT128,
+			BTT_SINT128,
+			BTT_UINT128,
+			BTT_S8,
+			BTT_U8,
+			BTT_S16,
+			BTT_U16,
+			BTT_S32,
+			BTT_U32,
+			BTT_S64,
+			BTT_U64,
+#define BTT_START_INT_EXT BTT_S8
+#define BTT_INT_EXTS "__int8_t", "__uint8_t", "__int16_t", "__uint16_t", "__int32_t", "__uint32_t", "__int64_t", "__uint64_t"
+			BTT_FLOAT,
+			BTT_CFLOAT,
+			BTT_IFLOAT,
+			BTT_DOUBLE,
+			BTT_CDOUBLE,
+			BTT_IDOUBLE,
+			BTT_LONGDOUBLE,
+			BTT_CLONGDOUBLE,
+			BTT_ILONGDOUBLE,
+			BTT_VA_LIST,
+		} builtin;
+#define LAST_BUILTIN BTT_VA_LIST
+		struct type_s *typ;
+		struct {
+			struct type_s *typ;
+			size_t array_sz; // -1 for VLA
+		} array;
+		struct struct_s *st;
+		struct {
+			struct type_s *ret;
+			size_t nargs; // -1 for no specification
+			struct type_s **args;
+			int has_varargs;
+		} fun;
+	} val;
+	size_info_t szinfo;
+} type_t;
+void type_del(type_t *typ);
+KHASH_MAP_DECLARE_STR(type_map, type_t*)
+void type_map_del(khash_t(type_map) *map);
+
+int type_t_equal(type_t*, type_t*);
+
+typedef struct st_member_s {
+	string_t *name; // May be NULL
+	type_t *typ;
+	_Bool is_bitfield;
+	size_t bitfield_width;
+} st_member_t;
+typedef struct struct_s {
+	string_t *tag;
+	int is_defined;
+	size_t nrefs;
+	int is_struct; // 0 = union, 1 = struct
+	int has_incomplete; // 1 if the last element of the structure is a VLA or if an element of the union recursively contains a VLA
+	int explicit_simple;
+	size_t nmembers;
+	st_member_t *members;
+} struct_t;
+void st_member_del(st_member_t *member);
+void struct_del(struct_t *st);
+KHASH_MAP_DECLARE_STR(struct_map, struct_t*)
+
+type_t *type_new(void); // Create a new (complete) builtin type
+type_t *type_new_ptr(type_t *target); // Create a new pointer type; doesn't increment the use counter of the target
+// type_t *type_do_copy(type_t *ref); // Always duplicate ref; decrements the use counter
+// type_t *type_do_copy_nodec(const type_t *ref); // Always duplicate ref; doesn't decrements the use counter
+// type_t *type_maybe_copy(type_t *ref); // Only duplicate ref if it is used elsewhere; in that case, decrements the use counter
+int type_copy_into(type_t *dest, const type_t *ref); // Copy ref into dest, keeping additional qualifiers and without changing any use counter
+
+struct_t *struct_new(int is_struct, string_t *tag); // Create a new struct
+
+// Try to merge some types with other types; this may delete ptr and increase a use counter in a type referenced by the table
+KHASH_DECLARE(type_set, type_t*, char)
+type_t *type_try_merge(type_t *ptr, khash_t(type_set) *set);
+
+extern const char *builtin2str[LAST_BUILTIN + 1];
+void type_print(type_t *typ);
+void struct_print(const struct_t *st);
+
+typedef struct file_s {
+	khash_t(struct_map) *struct_map;
+	khash_t(type_map) *type_map;
+	khash_t(type_map) *enum_map;
+	khash_t(type_map) *decl_map;
+	type_t *builtins[LAST_BUILTIN + 1];
+	khash_t(const_map) *const_map;
+	khash_t(type_set) *type_set;
+} file_t;
+file_t *file_new(void);
+void file_del(file_t *f);
+
+extern const char *sym2str[LAST_SYM + 1];
+extern const char *kw2str[LAST_KEYWORD + 1];
+void preproc_token_print(const preproc_token_t *tok);
+int preproc_token_isend(const preproc_token_t *tok);
+void proc_token_print(const proc_token_t *tok);
+int proc_token_iserror(const proc_token_t *tok);
+int proc_token_isend(const proc_token_t *tok);
+
+KHASH_MAP_DECLARE_STR(str2kw, enum token_keyword_type_e)
+extern khash_t(str2kw) *str2kw;
+int init_str2kw(void);
+void del_str2kw(void);
+
+#endif // LANG_H
diff --git a/wrapperhelper/src/main.c b/wrapperhelper/src/main.c
new file mode 100644
index 00000000..8a397b6b
--- /dev/null
+++ b/wrapperhelper/src/main.c
@@ -0,0 +1,184 @@
+#include <err.h>
+#include <errno.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "generator.h"
+#include "lang.h"
+#include "parse.h"
+#include "khash.h"
+
+static void help(char *arg0) {
+	printf("Usage: %s --help\n"
+	       "       %s [--prepare|--preproc|--proc] <filename_in>\n"
+	       "       %s <filename_in> <filename_reqs> <filename_out>\n"
+	       "\n"
+	       "  --prepare  Dump all preprocessor tokens (prepare phase)\n"
+	       "  --preproc  Dump all processor tokens (preprocessor phase)\n"
+	       "  --proc     Dump all typedefs, declarations and constants (processor phase)\n"
+	       "\n"
+	       "  <filename_in>    Parsing file\n"
+	       "  <filename_reqs>  Reference file (example: wrappedlibc_private.h)\n"
+	       "  <filename_out>   Output file\n",
+	       arg0, arg0, arg0);
+}
+
+enum main_state {
+	MAIN_RUN,
+	MAIN_PREPARE,
+	MAIN_PREPROC,
+	MAIN_PROC,
+};
+
+int main(int argc, char **argv) {
+	setbuf(stdout, NULL);
+	if (!setlocale(LC_NUMERIC, "C")) {
+		printf("Error: failed to set LC_NUMERIC to C\n");
+		return 2;
+	}
+	
+	enum main_state ms;
+	int off;
+	
+	if ((argc == 2) && !strcmp(argv[1], "--help")) {
+		help(argv[0]);
+		return 0;
+	} else if (argc == 2) {
+		ms = MAIN_PROC;
+		off = 1;
+	} else if ((argc == 3) && !strcmp(argv[1], "--prepare")) {
+		ms = MAIN_PREPARE;
+		off = 2;
+	} else if ((argc == 3) && !strcmp(argv[1], "--preproc")) {
+		ms = MAIN_PREPROC;
+		off = 2;
+	} else if ((argc == 3) && !strcmp(argv[1], "--proc")) {
+		ms = MAIN_PROC;
+		off = 2;
+	} else if (argc == 4) {
+		ms = MAIN_RUN;
+		off = 1;
+	} else {
+		help(argv[0]);
+		return 2;
+	}
+	
+	if (!init_str2kw()) {
+		return 2;
+	}
+	
+	FILE *f = fopen(argv[off], "r");
+	if (!f) {
+		err(2, "Error: failed to open %s", argv[off]);
+		return 2;
+	}
+	switch (ms) {
+	case MAIN_RUN: {
+		file_t *content = parse_file(argv[off], f); // Takes ownership of f
+		if (!content) {
+			printf("Error: failed to parse the file\n");
+			del_str2kw();
+			return 0;
+		}
+		
+		FILE *ref = fopen(argv[off + 1], "r");
+		if (!ref) {
+			err(2, "Error: failed to open %s", argv[off + 1]);
+			del_str2kw();
+			return 2;
+		}
+		VECTOR(requests) *reqs = requests_from_file(argv[off + 1], ref);
+		if (!reqs) {
+			file_del(content);
+			del_str2kw();
+			return 2;
+		}
+		// vector_for(requests, req, reqs) request_print(req);
+		if (!solve_requests(reqs, content->decl_map)) {
+			printf("Warning: failed to solve all default requests\n");
+		}
+		// vector_for(requests, req, reqs) request_print(req);
+		//vector_for(requests, req, reqs) request_print_check(req);
+		FILE *out = fopen(argv[off + 2], "w");
+		if (!out) {
+			err(2, "Error: failed to open %s", argv[off + 1]);
+			file_del(content);
+			vector_del(requests, reqs);
+			del_str2kw();
+			return 2;
+		}
+		output_from_requests(out, reqs);
+		fclose(out);
+		vector_del(requests, reqs);
+		file_del(content);
+		del_str2kw();
+		return 0; }
+	case MAIN_PROC: {
+		file_t *content = parse_file(argv[off], f); // Takes ownership of f
+		if (!content) {
+			printf("Error: failed to parse the file\n");
+			del_str2kw();
+			return 0;
+		}
+		// print content
+		const char *name;
+		struct_t *st;
+		type_t *typ;
+		num_constant_t cst;
+		/* for (enum type_builtin_e i = 0; i < LAST_BUILTIN; ++i) {
+			printf("Builtin %u: %p, ", i, content->builtins[i]);
+			type_print(content->builtins[i]);
+			printf("\n");
+		} */
+		kh_foreach(content->struct_map, name, st,
+			printf("Struct: %s -> %p = ", name, st);
+			struct_print(st);
+			printf("\n")
+		)
+		kh_foreach(content->type_map, name, typ,
+			printf("Typedef: %s -> %p = ", name, typ);
+			type_print(typ);
+			printf("\n")
+		)
+		kh_foreach(content->enum_map, name, typ,
+			printf("Enum: %s -> %p = ", name, typ);
+			type_print(typ);
+			printf("\n")
+		)
+		kh_foreach(content->const_map, name, cst,
+			printf("Constant: %s -> ", name);
+			switch (cst.typ) {
+			case NCT_FLOAT: printf("%ff", cst.val.f); break;
+			case NCT_DOUBLE: printf("%f", cst.val.d); break;
+			case NCT_LDOUBLE: printf("%Lfl", cst.val.l); break;
+			case NCT_INT32: printf("%d", cst.val.i32); break;
+			case NCT_UINT32: printf("%uu", cst.val.u32); break;
+			case NCT_INT64: printf("%ldll", cst.val.i64); break;
+			case NCT_UINT64: printf("%lullu", cst.val.u64); break;
+			}
+			printf("\n")
+		)
+		kh_foreach(content->decl_map, name, typ,
+			printf("Declaration: %s -> %p = ", name, typ);
+			type_print(typ);
+			printf("\n")
+		)
+		file_del(content);
+		del_str2kw();
+		return 0; }
+		
+	case MAIN_PREPARE:
+		dump_prepare(argv[off], f); // Takes ownership of f
+		del_str2kw();
+		return 0;
+		
+	case MAIN_PREPROC:
+		dump_preproc(argv[off], f); // Takes ownership of f
+		del_str2kw();
+		return 0;
+	}
+	
+	printf("<internal error> Failed to run mode %u\n", ms);
+	return 2;
+}
diff --git a/wrapperhelper/src/parse.c b/wrapperhelper/src/parse.c
new file mode 100644
index 00000000..cba0d9de
--- /dev/null
+++ b/wrapperhelper/src/parse.c
@@ -0,0 +1,3164 @@
+#include "parse.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include "cstring.h"
+#include "khash.h"
+#include "prepare.h"
+#include "preproc.h"
+
+void dump_prepare(const char *filename, FILE *file) {
+	prepare_t *prep = prepare_new_file(file, filename);
+	if (!prep) {
+		printf("Failed to create the prepare structure\n");
+		return;
+	}
+	while (1) {
+		preproc_token_t tok = pre_next_token(prep, 0);
+		preproc_token_print(&tok);
+		if (preproc_token_isend(&tok)) {
+			preproc_token_del(&tok);
+			prepare_del(prep);
+			return;
+		}
+		preproc_token_del(&tok);
+	}
+}
+void dump_preproc(const char *filename, FILE *file) {
+	char *dirname = strchr(filename, '/') ? strndup(filename, (size_t)(strrchr(filename, '/') - filename)) : NULL;
+	preproc_t *prep = preproc_new_file(file, dirname, filename);
+	if (!prep) {
+		printf("Failed to create the preproc structure\n");
+		if (dirname) free(dirname);
+		return;
+	}
+	while (1) {
+		proc_token_t tok = proc_next_token(prep);
+		proc_token_print(&tok);
+		if (proc_token_isend(&tok)) {
+			proc_token_del(&tok);
+			preproc_del(prep);
+			return;
+		}
+		proc_token_del(&tok);
+	}
+}
+
+enum decl_storage {
+	STORAGE_NONE,
+	STORAGE_TYPEDEF,
+	STORAGE_EXTERN,
+	STORAGE_STATIC,
+	STORAGE_TLS,
+	STORAGE_TLS_EXTERN,
+	STORAGE_TLS_STATIC,
+	STORAGE_AUTO,
+	STORAGE_REG,
+};
+enum decl_spec {
+	SPEC_NONE,
+	SPEC_BUILTIN,
+	SPEC_COMPLEX,
+	SPEC_IMAGINARY,
+	SPEC_LONGCOMPLEX,
+	SPEC_LONGIMAGINARY,
+	SPEC_BUILTIN_NOINT,
+	SPEC_TYPE,
+};
+VECTOR_DECLARE_STATIC(size_t, size_t)
+VECTOR_IMPL_STATIC(size_t, (void))
+
+#define VALIDATION_DECL 1
+#define VALIDATION_LAST_DECL 2
+#define VALIDATION_FUN 3
+// Assumes sizeof(void*) == sizeof(unsigned long)
+static int validate_type(type_t *typ, type_t *(*builtins)[LAST_BUILTIN + 1]) {
+	if (typ->is_validated) return 1;
+	typ->is_validated = 1;
+	if (typ->is_restrict) {
+		if (typ->typ != TYPE_PTR) {
+			printf("Error: only pointers to object types may be restrict-qualified\n");
+			return 0;
+		}
+		if (typ->val.typ->typ == TYPE_FUNCTION) {
+			printf("Error: only pointers to object types may be restrict-qualified\n");
+			return 0;
+		}
+	}
+	if (typ->is_atomic) {
+		if ((typ->typ == TYPE_ARRAY) || (typ->typ == TYPE_FUNCTION)) {
+			printf("Error: array types and function types may not be atomic-qualified\n");
+			return 0;
+		}
+	}
+	switch (typ->typ) {
+	case TYPE_BUILTIN:
+		typ->szinfo = (*builtins)[typ->val.builtin]->szinfo;
+		return 1;
+	case TYPE_ARRAY:
+		if (typ->val.array.typ->is_incomplete || (typ->val.array.typ->typ == TYPE_FUNCTION)) {
+			printf("Error: array types must point to complete object types\n");
+			return 0;
+		}
+		if ((typ->val.array.typ->typ == TYPE_STRUCT_UNION) && typ->val.array.typ->val.st->has_incomplete) {
+			printf("Error: array types may not (inductively) point to structures which last element is incomplete\n");
+			return 0;
+		}
+		if ((typ->is_atomic) || (typ->is_const) || (typ->is_restrict) || (typ->is_volatile)) {
+			// qualifier-type-list in array declaration is only allowed in function argument declaration under certain circumstances
+			printf("Error: array types may not be qualified\n");
+			return 0;
+		}
+		if (!validate_type(typ->val.array.typ, builtins)) return 0;
+		if (typ->val.array.array_sz == (size_t)-1) {
+			typ->szinfo.size = 0;
+			typ->szinfo.align = (typ->val.array.typ->szinfo.align < 16) ? 16 : typ->val.array.typ->szinfo.align;
+		} else {
+			typ->szinfo.size = typ->val.array.array_sz * typ->val.array.typ->szinfo.size;
+			typ->szinfo.align =
+				((typ->szinfo.size >= 16) && (typ->val.array.typ->szinfo.align < 16)) ?
+				16 :
+				typ->val.array.typ->szinfo.align;
+		}
+		return 1;
+	case TYPE_PTR:
+		typ->szinfo.size = LONG_IS_32BITS ? 4 : 8;
+		typ->szinfo.align = LONG_IS_32BITS ? 4 : 8;
+		return validate_type(typ->val.typ, builtins);
+	case TYPE_FUNCTION:
+		if ((typ->val.fun.ret->typ == TYPE_FUNCTION) || (typ->val.fun.ret->typ == TYPE_ARRAY)) {
+			printf("Error: function types may not return function or array types\n");
+			return 0;
+		}
+		if (typ->val.fun.nargs != (size_t)-1) {
+			for (size_t i = 0; i < typ->val.fun.nargs; ++i) {
+				// Adjust the argument if necessary
+				if (typ->val.fun.args[i]->typ == TYPE_ARRAY) {
+					// Adjustment to pointer
+					typ->val.fun.args[i]->typ = TYPE_PTR;
+					typ->val.fun.args[i]->val.typ = typ->val.fun.args[i]->val.array.typ;
+				} else if (typ->val.fun.args[i]->typ == TYPE_FUNCTION) {
+					// Adjustment to pointer
+					type_t *t2 = type_new_ptr(typ->val.fun.args[i]);
+					if (!t2) {
+						printf("Error: failed to adjust type of argument from function to pointer\n");
+						return 0;
+					}
+					typ->val.fun.args[i] = t2;
+				}
+				if (!validate_type(typ->val.fun.args[i], builtins)) return 0;
+			}
+		}
+		typ->szinfo.size = 0;
+		typ->szinfo.align = 0;
+		return validate_type(typ->val.fun.ret, builtins);
+	case TYPE_STRUCT_UNION: {
+		if (!typ->val.st->is_defined) return typ->is_incomplete;
+		size_t max_align = 1, cur_sz = 0, cur_bit = 0;
+		for (size_t i = 0; i < typ->val.st->nmembers; ++i) {
+			// Adjust the argument if necessary
+			st_member_t *mem = &typ->val.st->members[i];
+			if (mem->typ->typ == TYPE_FUNCTION) {
+				printf("Error: structures may not contain function members\n");
+				return 0;
+			}
+			if (mem->typ->is_incomplete) {
+				if ((i != typ->val.st->nmembers - 1) || !typ->val.st->is_struct || (mem->typ->typ != TYPE_ARRAY)) {
+					// The last element of a structure may be a VLA
+					printf("Error: structures may not contain incomplete members\n");
+					return 0;
+				}
+				typ->val.st->has_incomplete = 1;
+			}
+			if (!validate_type(mem->typ, builtins)) return 0;
+			if (!typ->val.st->is_struct && (mem->typ->typ == TYPE_STRUCT_UNION)) {
+				typ->val.st->has_incomplete |= mem->typ->val.st->has_incomplete;
+			}
+			if (mem->is_bitfield) {
+				if (!typ->val.st->is_struct) {
+					printf("Error: TODO: bitfield in union\n");
+					return 0;
+				}
+				if (mem->typ->is_atomic) {
+					printf("Error: atomic bitfields are not supported\n");
+					return 0;
+				}
+				if (mem->typ->typ != TYPE_BUILTIN) {
+					printf("Error: bitfields can only have a specific subset of types\n");
+					return 0;
+				}
+				if ((mem->typ->val.builtin != BTT_BOOL) && (mem->typ->val.builtin != BTT_INT)
+				 && (mem->typ->val.builtin != BTT_SINT) && (mem->typ->val.builtin != BTT_UINT)) {
+					printf("Error: bitfields can only have a specific subset of types\n");
+					return 0;
+				}
+				if (!mem->name && (mem->typ->szinfo.align > max_align)) {
+					printf("Error: TODO: unnamed bitfield member with greater alignment (width=%zu)\n", mem->bitfield_width);
+					return 0;
+				}
+				if (mem->bitfield_width) {
+					if (mem->name && (max_align < mem->typ->szinfo.align)) max_align = mem->typ->szinfo.align;
+					size_t cur_block = cur_sz / mem->typ->szinfo.align;
+					size_t end_block = (cur_sz + (cur_bit + mem->bitfield_width - 1) / 8) / mem->typ->szinfo.align;
+					if (cur_block == end_block) {
+						cur_bit += mem->bitfield_width;
+						cur_sz += cur_bit / 8;
+						cur_bit %= 8;
+					} else {
+						cur_sz = ((cur_sz + mem->typ->szinfo.align - 1) & ~(mem->typ->szinfo.align - 1)) + (mem->bitfield_width / 8);
+						cur_bit = mem->bitfield_width % 8;
+					}
+				} else {
+					if (max_align < mem->typ->szinfo.align) max_align = mem->typ->szinfo.align;
+					cur_sz = ((cur_sz + mem->typ->szinfo.align - 1) & ~(mem->typ->szinfo.align - 1)) + mem->typ->szinfo.size;
+					printf("Error: TODO: unnamed zero-width bitfield member\n");
+					return 0;
+				}
+			} else {
+				if (max_align < mem->typ->szinfo.align) max_align = mem->typ->szinfo.align;
+				if (typ->val.st->is_struct) {
+					if (cur_bit) {
+						cur_bit = 0;
+						++cur_sz;
+					}
+					cur_sz = ((cur_sz + mem->typ->szinfo.align - 1) & ~(mem->typ->szinfo.align - 1)) + mem->typ->szinfo.size;
+				} else {
+					if (cur_sz < mem->typ->szinfo.size) cur_sz = mem->typ->szinfo.size;
+				}
+			}
+		}
+		if (cur_bit) {
+			cur_bit = 0;
+			++cur_sz;
+		}
+		typ->szinfo.align = max_align;
+		typ->szinfo.size = (cur_sz + max_align - 1) & ~(max_align - 1);
+		return 1; }
+	case TYPE_ENUM:
+		if (typ->val.typ->typ != TYPE_BUILTIN) return 0;
+		typ->szinfo = typ->val.typ->szinfo;
+		return 1;
+	}
+	return 0;
+}
+static int validate_storage_type(enum decl_storage storage, type_t *(*builtins)[LAST_BUILTIN + 1], type_t *typ, enum token_sym_type_e sym) {
+	// We may still do adjustments here
+	if (!validate_type(typ, builtins)) return 0;
+	if (typ->typ == TYPE_FUNCTION) {
+		if ((storage == STORAGE_TLS) || (storage == STORAGE_TLS_EXTERN) || (storage == STORAGE_TLS_STATIC)) {
+			printf("Error: functions cannot be thread local\n");
+			return 0;
+		}
+		if ((sym == SYM_COMMA) || (sym == SYM_RPAREN) || (sym == SYM_SEMICOLON)) {
+			return (sym == SYM_SEMICOLON) ? VALIDATION_LAST_DECL : VALIDATION_DECL;
+		} else if (sym == SYM_LBRACKET) {
+			return VALIDATION_FUN;
+		}
+	} else {
+		if ((sym == SYM_COMMA) || (sym == SYM_RPAREN) || (sym == SYM_SEMICOLON) || ((storage != STORAGE_TYPEDEF) && (sym == SYM_EQ))) {
+			return (sym == SYM_SEMICOLON) ? VALIDATION_LAST_DECL : VALIDATION_DECL;
+		}
+	}
+	printf("TODO: validate_storage_type on storage %u, sym %s (%u), valid type ", storage, sym2str[sym], sym);
+	type_print(typ);
+	printf("\n");
+	return 0;
+}
+
+static void promote_csts(num_constant_t *v1, num_constant_t *v2) {
+	switch (v1->typ) {
+	case NCT_FLOAT:
+		switch (v2->typ) {
+		case NCT_FLOAT: break;
+		case NCT_DOUBLE: v1->typ = NCT_DOUBLE; v1->val.d = v1->val.f; break;
+		case NCT_LDOUBLE: v1->typ = NCT_LDOUBLE; v1->val.l = v1->val.f; break;
+		case NCT_INT32: v2->typ = NCT_FLOAT; v2->val.f = (float)v2->val.i32; break;
+		case NCT_UINT32: v2->typ = NCT_FLOAT; v2->val.f = (float)v2->val.u32; break;
+		case NCT_INT64: v2->typ = NCT_FLOAT; v2->val.f = (float)v2->val.i64; break;
+		case NCT_UINT64: v2->typ = NCT_FLOAT; v2->val.f = (float)v2->val.u64; break;
+		}
+		break;
+	case NCT_DOUBLE:
+		switch (v2->typ) {
+		case NCT_FLOAT: v2->typ = NCT_DOUBLE; v2->val.d = v2->val.f; break;
+		case NCT_DOUBLE: break;
+		case NCT_LDOUBLE: v1->typ = NCT_LDOUBLE; v1->val.l = v1->val.d; break;
+		case NCT_INT32: v2->typ = NCT_DOUBLE; v2->val.d = (double)v2->val.i32; break;
+		case NCT_UINT32: v2->typ = NCT_DOUBLE; v2->val.d = (double)v2->val.u32; break;
+		case NCT_INT64: v2->typ = NCT_DOUBLE; v2->val.d = (double)v2->val.i64; break;
+		case NCT_UINT64: v2->typ = NCT_DOUBLE; v2->val.d = (double)v2->val.u64; break;
+		}
+		break;
+	case NCT_LDOUBLE:
+		switch (v2->typ) {
+		case NCT_FLOAT: v2->typ = NCT_LDOUBLE; v2->val.l = v2->val.f; break;
+		case NCT_DOUBLE: v2->typ = NCT_LDOUBLE; v2->val.l = v2->val.d; break;
+		case NCT_LDOUBLE: break;
+		case NCT_INT32: v2->typ = NCT_LDOUBLE; v2->val.l = (long double)v2->val.i32; break;
+		case NCT_UINT32: v2->typ = NCT_LDOUBLE; v2->val.l = (long double)v2->val.u32; break;
+		case NCT_INT64: v2->typ = NCT_LDOUBLE; v2->val.l = (long double)v2->val.i64; break;
+		case NCT_UINT64: v2->typ = NCT_LDOUBLE; v2->val.l = (long double)v2->val.u64; break;
+		}
+		break;
+	case NCT_INT32:
+		switch (v2->typ) {
+		case NCT_FLOAT: v1->typ = NCT_FLOAT; v1->val.f = (float)v1->val.i32; break;
+		case NCT_DOUBLE: v1->typ = NCT_DOUBLE; v1->val.d = (double)v1->val.i32; break;
+		case NCT_LDOUBLE: v1->typ = NCT_LDOUBLE; v1->val.l = (long double)v1->val.i32; break;
+		case NCT_INT32: break;
+		case NCT_UINT32: v1->typ = NCT_UINT32; v1->val.u32 = (uint32_t)v1->val.i32; break;
+		case NCT_INT64: v1->typ = NCT_INT64; v1->val.i64 = v1->val.i32; break;
+		case NCT_UINT64: v1->typ = NCT_UINT64; v1->val.u64 = (uint64_t)v1->val.i32; break;
+		}
+		break;
+	case NCT_UINT32:
+		switch (v2->typ) {
+		case NCT_FLOAT: v1->typ = NCT_FLOAT; v1->val.f = (float)v1->val.u32; break;
+		case NCT_DOUBLE: v1->typ = NCT_DOUBLE; v1->val.d = (double)v1->val.u32; break;
+		case NCT_LDOUBLE: v1->typ = NCT_LDOUBLE; v1->val.l = (long double)v1->val.u32; break;
+		case NCT_INT32: v2->typ = NCT_UINT32; v2->val.u32 = (uint32_t)v2->val.i32; break;
+		case NCT_UINT32: break;
+		case NCT_INT64: v1->typ = NCT_INT64; v1->val.i64 = v1->val.u32; break;
+		case NCT_UINT64: v1->typ = NCT_UINT64; v1->val.u64 = v1->val.u32; break;
+		}
+		break;
+	case NCT_INT64:
+		switch (v2->typ) {
+		case NCT_FLOAT: v1->typ = NCT_FLOAT; v1->val.f = (float)v1->val.i64; break;
+		case NCT_DOUBLE: v1->typ = NCT_DOUBLE; v1->val.d = (double)v1->val.i64; break;
+		case NCT_LDOUBLE: v1->typ = NCT_LDOUBLE; v1->val.l = (long double)v1->val.i64; break;
+		case NCT_INT32: v2->typ = NCT_INT64; v2->val.i64 = v2->val.i32; break;
+		case NCT_UINT32: v2->typ = NCT_INT64; v2->val.i64 = v2->val.u32; break;
+		case NCT_INT64: break;
+		case NCT_UINT64: v1->typ = NCT_UINT64; v1->val.u64 = (uint64_t)v1->val.i64; break;
+		}
+		break;
+	case NCT_UINT64:
+		switch (v2->typ) {
+		case NCT_FLOAT: v1->typ = NCT_FLOAT; v1->val.f = (float)v1->val.u64; break;
+		case NCT_DOUBLE: v1->typ = NCT_DOUBLE; v1->val.d = (double)v1->val.u64; break;
+		case NCT_LDOUBLE: v1->typ = NCT_LDOUBLE; v1->val.l = (long double)v1->val.u64; break;
+		case NCT_INT32: v2->typ = NCT_UINT64; v2->val.u64 = (uint64_t)v2->val.i32; break;
+		case NCT_UINT32: v2->typ = NCT_UINT64; v2->val.u64 = v2->val.u32; break;
+		case NCT_INT64: v2->typ = NCT_UINT64; v2->val.u64 = (uint64_t)v2->val.i64; break;
+		case NCT_UINT64: break;
+		}
+		break;
+	}
+}
+
+VECTOR_DECLARE_STATIC(exprs, expr_t*)
+#define expr_del_ptr(p) expr_del(*(p))
+VECTOR_IMPL_STATIC(exprs, expr_del_ptr)
+#undef expr_del_ptr
+struct expr_partial_op {
+	enum expr_partial_op_e {
+		EPO_LPAREN,
+		EPO_SIZEOF,
+		EPO_CAST,
+		EPO_FUNCALL,
+		EPO_UNARY,
+		EPO_BINARY_ARG,
+		EPO_BINARY_ARG_SYM,
+		EPO_TERNARY_ARG1,
+		EPO_TERNARY_ARG2,
+	} typ;
+	int once_done_want_level;
+	int once_done_is_level;
+	union {
+		char c; // Empty destructor
+		type_t *typ;
+		struct {
+			expr_t *f;
+			VECTOR(exprs) *exprs;
+		} funcall;
+		enum unary_op_e unop;
+		struct {
+			enum binary_op_e op;
+			expr_t *e1;
+			enum token_sym_type_e last_sym;
+		} binop;
+		struct {
+			enum ternary_op_e op;
+			expr_t *e1;
+			expr_t *e2;
+			int once_done2_want_level;
+			enum token_sym_type_e arg23_sep;
+		} ternop;
+	} val;
+};
+void expr_partial_op_del(struct expr_partial_op *epo) {
+	switch (epo->typ) {
+	case EPO_LPAREN:
+	case EPO_SIZEOF:
+		return;
+	case EPO_CAST:
+		type_del(epo->val.typ);
+		return;
+	case EPO_FUNCALL:
+		expr_del(epo->val.funcall.f);
+		vector_del(exprs, epo->val.funcall.exprs);
+		return;
+	case EPO_UNARY:
+		return;
+	case EPO_BINARY_ARG:
+	case EPO_BINARY_ARG_SYM:
+		expr_del(epo->val.binop.e1);
+		return;
+	case EPO_TERNARY_ARG1:
+		expr_del(epo->val.ternop.e1);
+		return;
+	case EPO_TERNARY_ARG2:
+		expr_del(epo->val.ternop.e1);
+		expr_del(epo->val.ternop.e2);
+		return;
+	}
+}
+VECTOR_DECLARE_STATIC(expr_pops, struct expr_partial_op)
+VECTOR_IMPL_STATIC(expr_pops, expr_partial_op_del)
+
+VECTOR_DECLARE_STATIC(types, type_t*)
+#define type_ptr_del(t) type_del(*(t))
+VECTOR_IMPL_STATIC(types, type_ptr_del)
+#undef type_ptr_del
+VECTOR_DECLARE_STATIC(st_members, st_member_t)
+VECTOR_IMPL_STATIC(st_members, st_member_del)
+
+struct parse_declarator_dest_s {
+	file_t *f; // is_init, is_list
+	struct {
+		khash_t(struct_map) *struct_map;
+		khash_t(type_map) *type_map;
+		khash_t(type_map) *enum_map;
+		khash_t(type_set) *type_set;
+		type_t *(*builtins)[LAST_BUILTIN + 1];
+		khash_t(const_map) *const_map;
+		type_t *dest;
+	} argt; // !is_init, !is_list
+	struct {
+		khash_t(struct_map) *struct_map;
+		khash_t(type_map) *type_map;
+		khash_t(type_map) *enum_map;
+		khash_t(type_set) *type_set;
+		type_t *(*builtins)[LAST_BUILTIN + 1];
+		khash_t(const_map) *const_map;
+		VECTOR(st_members) *dest;
+	} structms; // !is_init, is_list
+};
+#define PDECL_STRUCT_MAP ((is_init && is_list) ? dest->f->struct_map : (!is_init && is_list) ? dest->structms.struct_map : dest->argt.struct_map)
+#define PDECL_TYPE_MAP ((is_init && is_list) ? dest->f->type_map : (!is_init && is_list) ? dest->structms.type_map : dest->argt.type_map)
+#define PDECL_ENUM_MAP ((is_init && is_list) ? dest->f->enum_map : (!is_init && is_list) ? dest->structms.enum_map : dest->argt.enum_map)
+#define PDECL_TYPE_SET ((is_init && is_list) ? dest->f->type_set : (!is_init && is_list) ? dest->structms.type_set : dest->argt.type_set)
+#define PDECL_BUILTINS ((is_init && is_list) ? &dest->f->builtins : (!is_init && is_list) ? dest->structms.builtins : dest->argt.builtins)
+#define PDECL_CONST_MAP ((is_init && is_list) ? dest->f->const_map : (!is_init && is_list) ? dest->structms.const_map : dest->argt.const_map)
+static int parse_declarator(struct parse_declarator_dest_s *dest, preproc_t *prep, proc_token_t *tok, enum decl_storage storage, type_t *base_type,
+      int is_init, int is_list, int allow_decl, int allow_abstract);
+
+// declaration-specifier with storage != NULL
+// specifier-qualifier-list + static_assert-declaration with storage == NULL
+static int parse_declaration_specifier(khash_t(struct_map) *struct_map, khash_t(type_map) *type_map, khash_t(type_map) *enum_map,
+        type_t *(*builtins)[LAST_BUILTIN + 1], khash_t(const_map) *const_map,
+        khash_t(type_set) *type_set, preproc_t *prep, proc_token_t *tok, enum decl_storage *storage, enum decl_spec *spec, type_t *typ);
+
+
+void expr_print(expr_t *e) {
+	switch (e->typ) {
+	case ETY_VAR: printf("%s", string_content(e->val.var)); break;
+	case ETY_CONST:
+		switch (e->val.cst.typ) {
+		case NCT_FLOAT: printf("%ff", e->val.cst.val.f); break;
+		case NCT_DOUBLE: printf("%f", e->val.cst.val.d); break;
+		case NCT_LDOUBLE: printf("%Lfl", e->val.cst.val.l); break;
+		case NCT_INT32: printf("%d", e->val.cst.val.i32); break;
+		case NCT_UINT32: printf("%uu", e->val.cst.val.u32); break;
+		case NCT_INT64: printf("%ldll", e->val.cst.val.i64); break;
+		case NCT_UINT64: printf("%lullu", e->val.cst.val.u64); break;
+		}
+		break;
+	case ETY_CALL: printf("(call)"); break;
+	case ETY_ACCESS: printf("(.)"); break;
+	case ETY_PTRACCESS: printf("(->)"); break;
+	case ETY_UNARY:
+		switch (e->val.unary.typ) {
+		case UOT_POSTINCR: printf("(++)"); expr_print(e->val.unary.e); break;
+		case UOT_POSTDECR: printf("(--)"); expr_print(e->val.unary.e); break;
+		case UOT_PREINCR: printf("++"); expr_print(e->val.unary.e); break;
+		case UOT_PREDECR: printf("--"); expr_print(e->val.unary.e); break;
+		case UOT_REF: printf("&"); expr_print(e->val.unary.e); break;
+		case UOT_POS: printf("(+)"); expr_print(e->val.unary.e); break;
+		case UOT_NEG: printf("(-)"); expr_print(e->val.unary.e); break;
+		case UOT_DEREF: printf("(*)"); expr_print(e->val.unary.e); break;
+		case UOT_ANOT: printf("~"); expr_print(e->val.unary.e); break;
+		case UOT_BNOT: printf("!"); expr_print(e->val.unary.e); break;
+		}
+		break;
+	case ETY_BINARY: {
+		printf("(");
+		expr_print(e->val.binary.e1);
+		switch (e->val.binary.typ) {
+		case BOT_ADD: printf(") + ("); break;
+		case BOT_SUB: printf(") - ("); break;
+		case BOT_MUL: printf(") * ("); break;
+		case BOT_DIV: printf(") / ("); break;
+		case BOT_MOD: printf(") %% ("); break;
+		case BOT_LSH: printf(") << ("); break;
+		case BOT_RSH: printf(") >> ("); break;
+		case BOT_LT: printf(") < ("); break;
+		case BOT_GT: printf(") > ("); break;
+		case BOT_LE: printf(") <= ("); break;
+		case BOT_GE: printf(") >= ("); break;
+		case BOT_EQ: printf(") == ("); break;
+		case BOT_NE: printf(") != ("); break;
+		case BOT_AAND: printf(") & ("); break;
+		case BOT_AXOR: printf(") ^ ("); break;
+		case BOT_AOR: printf(") | ("); break;
+		case BOT_BAND: printf(") && ("); break;
+		case BOT_BOR: printf(") || ("); break;
+		case BOT_ASSGN_EQ: printf(") = ("); break;
+		case BOT_ASSGN_ADD: printf(") += ("); break;
+		case BOT_ASSGN_SUB: printf(") -= ("); break;
+		case BOT_ASSGN_MUL: printf(") *= ("); break;
+		case BOT_ASSGN_DIV: printf(") /= ("); break;
+		case BOT_ASSGN_MOD: printf(") %%= ("); break;
+		case BOT_ASSGN_LSH: printf(") <<= ("); break;
+		case BOT_ASSGN_RSH: printf(") >>= ("); break;
+		case BOT_ASSGN_AAND: printf(") &= ("); break;
+		case BOT_ASSGN_AXOR: printf(") ^= ("); break;
+		case BOT_ASSGN_AOR: printf(") |= ("); break;
+		case BOT_COMMA: printf("), ("); break;
+		case BOT_ARRAY: printf(")["); break;
+		}
+		expr_print(e->val.binary.e2);
+		printf(e->val.binary.typ == BOT_ARRAY ? "]" : ")");
+		break; }
+	case ETY_TERNARY: {
+		printf("(");
+		expr_print(e->val.ternary.e1);
+		switch (e->val.ternary.typ) {
+		case TOT_COND: printf(") ? "); break;
+		}
+		expr_print(e->val.ternary.e2);
+		switch (e->val.ternary.typ) {
+		case TOT_COND: printf(" : ("); break;
+		}
+		expr_print(e->val.ternary.e3);
+		printf(")");
+		break; }
+	case ETY_CAST: printf("("); type_print(e->val.cast.typ); printf(")"); expr_print(e->val.cast.e); break;
+	}
+}
+
+
+static int is_type_spec_qual_kw(enum token_keyword_type_e kw) {
+	return
+		(kw == KW_ATOMIC)    ||
+		(kw == KW_BOOL)      ||
+		(kw == KW_CHAR)      ||
+		(kw == KW_COMPLEX)   ||
+		(kw == KW_CONST)     ||
+		(kw == KW_DOUBLE)    ||
+		(kw == KW_ENUM)      ||
+		(kw == KW_FLOAT)     ||
+		(kw == KW_IMAGINARY) ||
+		(kw == KW_INT)       ||
+		(kw == KW_INT128)    ||
+		(kw == KW_LONG)      ||
+		(kw == KW_RESTRICT)  ||
+		(kw == KW_SHORT)     ||
+		(kw == KW_SIGNED)    ||
+		(kw == KW_STRUCT)    ||
+		(kw == KW_UNION)     ||
+		(kw == KW_UNSIGNED)  ||
+		(kw == KW_VOID)      ||
+		(kw == KW_VOLATILE)  ||
+		0;
+}
+#define IS_BEGIN_TYPE_NAME \
+	(((tok->tokt == PTOK_KEYWORD) && is_type_spec_qual_kw(tok->tokv.kw)) || \
+	 ((tok->tokt == PTOK_IDENT) && ((it = kh_get(type_map, type_map, string_content(tok->tokv.str))) != kh_end(type_map))))
+static int parse_type_name(khash_t(struct_map) *struct_map, khash_t(type_map) *type_map, khash_t(type_map) *enum_map,
+        type_t *(*builtins)[LAST_BUILTIN + 1], khash_t(const_map) *const_map,
+        khash_t(type_set) *type_set, preproc_t *prep, proc_token_t *tok, enum token_sym_type_e end_sym, type_t **typ) {
+	enum decl_spec spec = SPEC_NONE;
+	if (!parse_declaration_specifier(struct_map, type_map, enum_map, builtins, const_map, type_set, prep, tok, NULL, &spec, *typ)) {
+		type_del(*typ);
+		goto failed;
+	}
+	*typ = type_try_merge(*typ, type_set);
+	if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == end_sym)) {
+		return validate_type(*typ, builtins);
+	}
+	struct parse_declarator_dest_s dest2;
+	dest2.argt.dest = NULL;
+	dest2.argt.struct_map = struct_map;
+	dest2.argt.type_map = type_map;
+	dest2.argt.enum_map = enum_map;
+	dest2.argt.type_set = type_set;
+	dest2.argt.builtins = builtins;
+	dest2.argt.const_map = const_map;
+	if (!parse_declarator(&dest2, prep, tok, STORAGE_NONE, *typ, 0, 0, 0, 1)) {
+		// Token is deleted
+		type_del(*typ);
+		goto failed;
+	}
+	type_del(*typ);
+	if (!dest2.argt.dest) {
+		printf("Internal error: argument type is NULL\n");
+		// Empty destructor
+		goto failed;
+	}
+	if ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != end_sym)) {
+		type_del(dest2.argt.dest);
+		proc_token_del(tok);
+		goto failed;
+	}
+	*typ = dest2.argt.dest;
+	*typ = type_try_merge(*typ, type_set);
+	return validate_type(*typ, builtins);
+	
+failed:
+	return 0;
+}
+
+static expr_t *parse_expression(khash_t(struct_map) *struct_map, khash_t(type_map) *type_map, khash_t(type_map) *enum_map,
+        type_t *(*builtins)[LAST_BUILTIN + 1], khash_t(const_map) *const_map,
+        khash_t(type_set) *type_set, preproc_t *prep, proc_token_t *tok, int expr_level) {
+	// Note that expr_level >= 1; expr_level = 0 doesn't appear in the grammar
+	if ((expr_level < 1) || (expr_level > 16)) {
+		printf("Internal error: invalid expression level %d\n", expr_level);
+		return NULL;
+	}
+	
+	VECTOR(expr_pops) *op_stack = vector_new(expr_pops);
+	if (!op_stack) {
+		printf("Error: failed to create operation stack");
+		proc_token_del(tok);
+		return NULL;
+	}
+	
+	int has_level;
+	expr_t *e;
+pushed_expr:
+	has_level = -1;
+	e = NULL;
+	
+expr_new_token:
+	// printf("expr_new_token with level %d / %d\n", has_level, expr_level);
+	// proc_token_print(tok);
+	if (tok->tokt == PTOK_IDENT) {
+		if (has_level != -1) {
+			printf("Error: invalid expression: unexpected identifier '%s'\n", string_content(tok->tokv.str));
+			string_del(tok->tokv.str);
+			goto failed;
+		}
+		has_level = 0;
+		e = malloc(sizeof *e);
+		if (!e) {
+			printf("Error: failed to create new expression atom\n");
+			string_del(tok->tokv.str);
+			goto failed;
+		}
+		e->typ = ETY_VAR;
+		e->val.var = tok->tokv.str;
+		*tok = proc_next_token(prep);
+		goto expr_new_token;
+	} else if (tok->tokt == PTOK_NUM) {
+		if (has_level != -1) {
+			printf("Error: invalid expression: unexpected number '%s'\n", string_content(tok->tokv.str));
+			string_del(tok->tokv.str);
+			goto failed;
+		}
+		has_level = 0;
+		e = malloc(sizeof *e);
+		if (!e) {
+			printf("Error: failed to create new expression atom\n");
+			string_del(tok->tokv.str);
+			goto failed;
+		}
+		e->typ = ETY_CONST;
+		if (!num_constant_convert(tok->tokv.str, &e->val.cst)) {
+			string_del(tok->tokv.str);
+			goto failed;
+		}
+		string_del(tok->tokv.str);
+		*tok = proc_next_token(prep);
+		goto expr_new_token;
+	}
+	
+#define UNOP(toksym, opt, main_lv, right_lv) \
+	if ((expr_level >= main_lv) && (tok->tokt == PTOK_SYM) && (tok->tokv.sym == toksym)) { \
+		if (has_level == -1) {                                                             \
+			struct expr_partial_op pop = {                                                 \
+				.once_done_want_level = expr_level,                                        \
+				.once_done_is_level = main_lv,                                             \
+				.typ = EPO_UNARY,                                                          \
+				.val.unop = opt                                                            \
+			};                                                                             \
+			if (!vector_push(expr_pops, op_stack, pop)) {                                  \
+				printf("Error: failed to add partial operation to operation stack\n");     \
+				/* Empty destructor */                                                     \
+				goto failed;                                                               \
+			}                                                                              \
+			expr_level = right_lv;                                                         \
+			*tok = proc_next_token(prep);                                                  \
+			goto pushed_expr;                                                              \
+		}                                                                                  \
+	}
+#define BINOP(toksym, opt, main_lv, left_lv, right_lv) \
+	if ((expr_level >= main_lv) && (tok->tokt == PTOK_SYM) && (tok->tokv.sym == toksym)) { \
+		if ((has_level != -1) && (has_level <= left_lv)) {                                 \
+			struct expr_partial_op pop = {                                                 \
+				.once_done_want_level = expr_level,                                        \
+				.once_done_is_level = main_lv,                                             \
+				.typ = EPO_BINARY_ARG,                                                     \
+				.val.binop = {.op = opt, .e1 = e}                                          \
+			};                                                                             \
+			if (!vector_push(expr_pops, op_stack, pop)) {                                  \
+				printf("Error: failed to add partial operation to operation stack\n");     \
+				/* Empty destructor */                                                     \
+				goto failed;                                                               \
+			}                                                                              \
+			expr_level = right_lv;                                                         \
+			*tok = proc_next_token(prep);                                                  \
+			goto pushed_expr;                                                              \
+		}                                                                                  \
+	}
+	if ((expr_level >= 1) && (tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_PLUSPLUS)) {
+		if ((has_level != -1) && (has_level <= 1)) {
+			has_level = 1;
+			expr_t *new_e = malloc(sizeof *new_e);
+			if (!new_e) {
+				printf("Error: failed to create new expression atom\n");
+				string_del(tok->tokv.str);
+				goto failed;
+			}
+			new_e->typ = ETY_UNARY;
+			new_e->val.unary.typ = UOT_POSTINCR;
+			new_e->val.unary.e = e;
+			e = new_e;
+			*tok = proc_next_token(prep);
+			goto expr_new_token;
+		}
+	}
+	if ((expr_level >= 1) && (tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_DASHDASH)) {
+		if ((has_level != -1) && (has_level <= 1)) {
+			has_level = 1;
+			expr_t *new_e = malloc(sizeof *new_e);
+			if (!new_e) {
+				printf("Error: failed to create new expression atom\n");
+				string_del(tok->tokv.str);
+				goto failed;
+			}
+			new_e->typ = ETY_UNARY;
+			new_e->val.unary.typ = UOT_POSTDECR;
+			new_e->val.unary.e = e;
+			e = new_e;
+			*tok = proc_next_token(prep);
+			goto expr_new_token;
+		}
+	}
+	if ((expr_level >= 1) && (tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_LSQBRACKET)) {
+		if ((has_level != -1) && (has_level <= 1)) {
+			struct expr_partial_op pop = {
+				.once_done_want_level = expr_level,
+				.once_done_is_level = 1,
+				.typ = EPO_BINARY_ARG_SYM,
+				.val.binop = {.last_sym = SYM_RSQBRACKET, .op = BOT_ARRAY, .e1 = e}
+			};
+			if (!vector_push(expr_pops, op_stack, pop)) {
+				printf("Error: failed to add partial operation to operation stack\n");
+				/* Empty destructor */
+				goto failed;
+			}
+			expr_level = 16;
+			*tok = proc_next_token(prep);
+			goto pushed_expr;
+		}
+	}
+	if ((expr_level >= 1) && (tok->tokt == PTOK_SYM) && ((tok->tokv.sym == SYM_DOT) || (tok->tokv.sym == SYM_DASHGT))) {
+		if ((has_level != -1) && (has_level <= 1)) {
+			has_level = 1;
+			int is_ptr = tok->tokv.sym == SYM_DASHGT;
+			
+			*tok = proc_next_token(prep);
+			if (tok->tokt != PTOK_IDENT) {
+				printf("Error: invalid expression: unexpected token after access symbol\n");
+				proc_token_del(tok);
+				goto failed;
+			}
+			expr_t *new_e = malloc(sizeof *new_e);
+			if (!new_e) {
+				printf("Error: failed to create new expression atom\n");
+				string_del(tok->tokv.str);
+				goto failed;
+			}
+			new_e->typ = is_ptr ? ETY_PTRACCESS : ETY_ACCESS;
+			new_e->val.access.val = e;
+			new_e->val.access.member = tok->tokv.str;
+			e = new_e;
+			*tok = proc_next_token(prep);
+			goto expr_new_token;
+		}
+	}
+	UNOP(SYM_PLUSPLUS, UOT_PREINCR, 2, 2)
+	UNOP(SYM_DASHDASH, UOT_PREDECR, 2, 2)
+	UNOP(SYM_AMP,      UOT_REF,     2, 3)
+	UNOP(SYM_PLUS,     UOT_POS,     2, 3)
+	UNOP(SYM_DASH,     UOT_NEG,     2, 3)
+	UNOP(SYM_STAR,     UOT_DEREF,   2, 3)
+	UNOP(SYM_TILDE,    UOT_ANOT,    2, 3)
+	UNOP(SYM_EXCL,     UOT_BNOT,    2, 3)
+	BINOP(SYM_STAR,     BOT_MUL,    4,  4,  3)
+	BINOP(SYM_SLASH,    BOT_DIV,    4,  4,  3)
+	BINOP(SYM_PERCENT,  BOT_MOD,    4,  4,  3)
+	BINOP(SYM_PLUS,     BOT_ADD,    5,  5,  4)
+	BINOP(SYM_DASH,     BOT_SUB,    5,  5,  4)
+	BINOP(SYM_LTLT,     BOT_LSH,    6,  6,  5)
+	BINOP(SYM_GTGT,     BOT_RSH,    6,  6,  5)
+	BINOP(SYM_LT,       BOT_LT,     7,  7,  6)
+	BINOP(SYM_GT,       BOT_GT,     7,  7,  6)
+	BINOP(SYM_LTEQ,     BOT_LE,     7,  7,  6)
+	BINOP(SYM_GTEQ,     BOT_GE,     7,  7,  6)
+	BINOP(SYM_EQEQ,     BOT_EQ,     8,  8,  7)
+	BINOP(SYM_EXCLEQ,   BOT_NE,     8,  8,  7)
+	BINOP(SYM_AMP,      BOT_AAND,   9,  9,  8)
+	BINOP(SYM_HAT,      BOT_AXOR,  10, 10,  9)
+	BINOP(SYM_PIPE,     BOT_AOR,   11, 11, 10)
+	BINOP(SYM_AMPAMP,   BOT_BAND,  12, 12, 11)
+	BINOP(SYM_PIPEPIPE, BOT_BOR,   13, 13, 12)
+	if ((expr_level >= 14) && (tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_QUESTION)) {
+		if ((has_level != -1) && (has_level <= 13)) {
+			struct expr_partial_op pop = {
+				.once_done_want_level = expr_level,
+				.once_done_is_level = 14,
+				.typ = EPO_TERNARY_ARG1,
+				.val.ternop = {.arg23_sep = SYM_COLON, .once_done2_want_level = 14, .op = TOT_COND, .e1 = e}
+			};
+			if (!vector_push(expr_pops, op_stack, pop)) {
+				printf("Error: failed to add partial operation to operation stack\n");
+				// Empty destructor
+				goto failed;
+			}
+			expr_level = 16;
+			*tok = proc_next_token(prep);
+			goto pushed_expr;
+		}
+	}
+	BINOP(SYM_EQ,        BOT_ASSGN_EQ,   15, 2, 15)
+	BINOP(SYM_STAREQ,    BOT_ASSGN_MUL,  15, 2, 15)
+	BINOP(SYM_SLASHEQ,   BOT_ASSGN_DIV,  15, 2, 15)
+	BINOP(SYM_PERCENTEQ, BOT_ASSGN_MOD,  15, 2, 15)
+	BINOP(SYM_PLUSEQ,    BOT_ASSGN_ADD,  15, 2, 15)
+	BINOP(SYM_DASHEQ,    BOT_ASSGN_SUB,  15, 2, 15)
+	BINOP(SYM_LTLTEQ,    BOT_ASSGN_LSH,  15, 2, 15)
+	BINOP(SYM_GTGTEQ,    BOT_ASSGN_RSH,  15, 2, 15)
+	BINOP(SYM_AMPEQ,     BOT_ASSGN_AAND, 15, 2, 15)
+	BINOP(SYM_HATEQ,     BOT_ASSGN_AXOR, 15, 2, 15)
+	BINOP(SYM_PIPEEQ,    BOT_ASSGN_AOR,  15, 2, 15)
+	BINOP(SYM_COMMA,    BOT_COMMA, 16, 16, 15)
+	
+	// expr2 ::= sizeof expr2
+	// which includes expr2 ::= sizeof ( expr16 )
+	// expr2 ::= sizeof ( type-name )
+	if ((has_level == -1) && (expr_level >= 2) && (tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw = KW_SIZEOF)) {
+		*tok = proc_next_token(prep);
+		if ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != SYM_LPAREN)) {
+			struct expr_partial_op pop = {
+				.once_done_want_level = expr_level,
+				.once_done_is_level = 2,
+				.typ = EPO_SIZEOF,
+				.val.c = '\0'
+			};
+			if (!vector_push(expr_pops, op_stack, pop)) {
+				printf("Error: failed to add partial operation to operation stack\n");
+				proc_token_del(tok);
+				goto failed;
+			}
+			expr_level = 2;
+			goto pushed_expr;
+		}
+		// Empty destructor
+		*tok = proc_next_token(prep);
+		khiter_t it;
+		if (IS_BEGIN_TYPE_NAME) {
+			type_t *typ = type_new();
+			if (!typ) {
+				printf("Error: failed to create new type info structure\n");
+				proc_token_del(tok);
+				goto failed;
+			}
+			if (!parse_type_name(struct_map, type_map, enum_map, builtins, const_map, type_set, prep, tok, SYM_RPAREN, &typ)) {
+				proc_token_del(tok);
+				goto failed;
+			}
+			if (!typ->is_validated || typ->is_incomplete) {
+				printf("Error: cannot get the size of an incomplete type\n");
+				proc_token_del(tok);
+				goto failed;
+			}
+			e = malloc(sizeof *e);
+			if (!e) {
+				printf("Error: failed to create new expression atom\n");
+				type_del(typ);
+				proc_token_del(tok);
+				goto failed;
+			}
+			e->typ = ETY_CONST;
+			e->val.cst.typ = NCT_UINT64;
+			e->val.cst.val.u64 = typ->szinfo.size;
+			has_level = 2;
+			type_del(typ);
+			if (!e->val.cst.val.u64) {
+				proc_token_del(tok);
+				goto failed;
+			}
+			// Empty destructor
+			*tok = proc_next_token(prep);
+			goto expr_new_token;
+		} else {
+			struct expr_partial_op pop = {
+				.once_done_want_level = expr_level,
+				.once_done_is_level = 2,
+				.typ = EPO_SIZEOF,
+				.val.c = 0
+			};
+			if (!vector_push(expr_pops, op_stack, pop)) {
+				printf("Error: failed to add partial operation to operation stack\n");
+				proc_token_del(tok);
+				goto failed;
+			}
+			pop = (struct expr_partial_op){
+				.once_done_want_level = 2,
+				.once_done_is_level = 0,
+				.typ = EPO_LPAREN,
+				.val.c = 0
+			};
+			if (!vector_push(expr_pops, op_stack, pop)) {
+				printf("Error: failed to add partial operation to operation stack\n");
+				proc_token_del(tok);
+				goto failed;
+			}
+			expr_level = 16;
+			goto pushed_expr;
+		}
+	}
+	
+	// expr0 ::= ( expr16 )
+	// expr1 ::= expr1 ( )
+	// expr1 ::= expr1 ( expr15 {, expr15}* )
+	// expr1 ::= ( type-name ) { initializer-list ,? }
+	// expr3 ::= ( type-name ) expr3
+	if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_LPAREN)) {
+		if ((has_level != -1) && (expr_level >= 1) && (has_level <= 1)) {
+			// Empty destructor
+			*tok = proc_next_token(prep);
+			if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) {
+				expr_t *new_e = malloc(sizeof *new_e);
+				new_e = malloc(sizeof *new_e);
+				if (!new_e) {
+					printf("Error: failed to create new expression atom\n");
+					// Empty destructor
+					goto failed;
+				}
+				new_e->typ = ETY_CALL;
+				new_e->val.call.fun = e;
+				new_e->val.call.nargs = 0;
+				new_e->val.call.args = NULL;
+				e = new_e;
+				*tok = proc_next_token(prep);
+				goto expr_new_token;
+			}
+			// We want a function call with at least one argument
+			VECTOR(exprs) *exprs = vector_new_cap(exprs, 1);
+			if (!exprs) {
+				printf("Error: failed to add partial operation to operation stack\n");
+				proc_token_del(tok);
+				goto failed;
+			}
+			struct expr_partial_op pop = {
+				.once_done_want_level = expr_level,
+				.once_done_is_level = 1,
+				.typ = EPO_FUNCALL,
+				.val.funcall = {.f = e, .exprs = exprs}
+			};
+			if (!vector_push(expr_pops, op_stack, pop)) {
+				printf("Error: failed to add partial operation to operation stack\n");
+				vector_del(exprs, exprs);
+				proc_token_del(tok);
+				goto failed;
+			}
+			expr_level = 15;
+			goto pushed_expr;
+		}
+		if (has_level == -1) {
+			*tok = proc_next_token(prep);
+			khiter_t it;
+			if ((expr_level >= 1) && IS_BEGIN_TYPE_NAME) {
+				type_t *typ = type_new();
+				if (!typ) {
+					printf("Error: failed to create new type info structure\n");
+					proc_token_del(tok);
+					goto failed;
+				}
+				if (!parse_type_name(struct_map, type_map, enum_map, builtins, const_map, type_set, prep, tok, SYM_RPAREN, &typ)) {
+					type_del(typ);
+					proc_token_del(tok);
+					goto failed;
+				}
+				if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_LBRACKET)) {
+					printf("Error: TODO: initializer-list\n");
+					type_del(typ);
+					proc_token_del(tok);
+					goto failed;
+				} else if (expr_level < 3) {
+					printf("Error: cast expression (level 3) but the expression level is at most 2\n");
+					type_del(typ);
+					proc_token_del(tok);
+					goto failed;
+				} else {
+					struct expr_partial_op pop = {
+						.once_done_want_level = expr_level,
+						.once_done_is_level = 3,
+						.typ = EPO_CAST,
+						.val.typ = typ
+					};
+					if (!vector_push(expr_pops, op_stack, pop)) {
+						printf("Error: failed to add partial operation to operation stack\n");
+						/* Empty destructor */
+						goto failed;
+					}
+					expr_level = 3;
+					*tok = proc_next_token(prep);
+					goto pushed_expr;
+				}
+			} else {
+				struct expr_partial_op pop = {
+					.once_done_want_level = expr_level,
+					.once_done_is_level = 0,
+					.typ = EPO_LPAREN,
+					.val.c = 0
+				};
+				if (!vector_push(expr_pops, op_stack, pop)) {
+					printf("Error: failed to add partial operation to operation stack\n");
+					proc_token_del(tok);
+					goto failed;
+				}
+				expr_level = 16;
+				goto pushed_expr;
+			}
+		}
+	}
+	
+	if (vector_size(expr_pops, op_stack)) {
+		if (has_level != -1) {
+			struct expr_partial_op *pop = &vector_last(expr_pops, op_stack);
+			expr_t *new_e;
+			switch (pop->typ) {
+			case EPO_LPAREN:
+				if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) {
+					has_level = pop->once_done_is_level;
+					expr_level = pop->once_done_want_level;
+					vector_pop_nodel(expr_pops, op_stack);
+					// Empty destructor
+					*tok = proc_next_token(prep);
+					goto expr_new_token;
+				}
+				break;
+			case EPO_SIZEOF:
+				printf("Error: TODO: find type of expression\n");
+				proc_token_del(tok);
+				goto failed;
+			case EPO_CAST:
+				new_e = malloc(sizeof *new_e);
+				if (!new_e) {
+					printf("Error: failed to create new expression atom\n");
+					proc_token_del(tok);
+					goto failed;
+				}
+				has_level = pop->once_done_is_level;
+				expr_level = pop->once_done_want_level;
+				new_e->typ = ETY_CAST;
+				new_e->val.cast.typ = pop->val.typ;
+				new_e->val.cast.e = e;
+				e = new_e;
+				vector_pop_nodel(expr_pops, op_stack);
+				// Keep the same token
+				goto expr_new_token;
+			case EPO_FUNCALL:
+				if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) {
+					has_level = pop->once_done_is_level;
+					expr_level = pop->once_done_want_level;
+					if (!vector_push(exprs, pop->val.funcall.exprs, e)) {
+						printf("Error: failed to add argument to argument stack\n");
+						// Empty destructor
+						goto failed;
+					}
+					e = malloc(sizeof *e);
+					if (!e) {
+						printf("Error: failed to create new expression atom\n");
+						// Empty destructor
+						goto failed;
+					}
+					e->typ = ETY_CALL;
+					e->val.call.fun = pop->val.funcall.f;
+					e->val.call.nargs = vector_size(exprs, pop->val.funcall.exprs);
+					e->val.call.args = vector_steal(exprs, pop->val.funcall.exprs);
+					vector_pop_nodel(expr_pops, op_stack);
+					// Empty destructor
+					*tok = proc_next_token(prep);
+					goto expr_new_token;
+				} else if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_COMMA)) {
+					if (!vector_push(exprs, pop->val.funcall.exprs, e)) {
+						printf("Error: failed to add argument to argument stack\n");
+						// Empty destructor
+						goto failed;
+					}
+					// expr_level is already 15
+					// Empty destructor
+					*tok = proc_next_token(prep);
+					goto pushed_expr;
+				}
+				break;
+			case EPO_UNARY:
+				new_e = malloc(sizeof *new_e);
+				if (!new_e) {
+					printf("Error: failed to create new expression atom\n");
+					proc_token_del(tok);
+					goto failed;
+				}
+				has_level = pop->once_done_is_level;
+				expr_level = pop->once_done_want_level;
+				new_e->typ = ETY_UNARY;
+				new_e->val.unary.typ = pop->val.unop;
+				new_e->val.unary.e = e;
+				e = new_e;
+				vector_pop_nodel(expr_pops, op_stack);
+				// Keep the same token
+				goto expr_new_token;
+			case EPO_BINARY_ARG_SYM:
+				if ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != pop->val.binop.last_sym)) break;
+				// Empty destructor
+				*tok = proc_next_token(prep);
+				/* FALLTHROUGH */
+			case EPO_BINARY_ARG:
+				new_e = malloc(sizeof *new_e);
+				if (!new_e) {
+					printf("Error: failed to create new expression atom\n");
+					proc_token_del(tok);
+					goto failed;
+				}
+				has_level = pop->once_done_is_level;
+				expr_level = pop->once_done_want_level;
+				new_e->typ = ETY_BINARY;
+				new_e->val.binary.typ = pop->val.binop.op;
+				new_e->val.binary.e1 = pop->val.binop.e1;
+				new_e->val.binary.e2 = e;
+				e = new_e;
+				vector_pop_nodel(expr_pops, op_stack);
+				// Keep the same token
+				goto expr_new_token;
+			case EPO_TERNARY_ARG1:
+				if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == pop->val.ternop.arg23_sep)) {
+					pop->typ = EPO_TERNARY_ARG2;
+					pop->val.ternop.e2 = e;
+					expr_level = pop->val.ternop.once_done2_want_level;
+					// Empty destructor
+					*tok = proc_next_token(prep);
+					goto pushed_expr;
+				}
+				break;
+			case EPO_TERNARY_ARG2:
+				new_e = malloc(sizeof *new_e);
+				if (!new_e) {
+					printf("Error: failed to create new expression atom\n");
+					proc_token_del(tok);
+					goto failed;
+				}
+				has_level = pop->once_done_is_level;
+				expr_level = pop->once_done_want_level;
+				new_e->typ = ETY_TERNARY;
+				new_e->val.ternary.typ = pop->val.ternop.op;
+				new_e->val.ternary.e1 = pop->val.ternop.e1;
+				new_e->val.ternary.e2 = pop->val.ternop.e2;
+				new_e->val.ternary.e3 = e;
+				e = new_e;
+				vector_pop_nodel(expr_pops, op_stack);
+				// Keep the same token
+				goto expr_new_token;
+			}
+		}
+	}
+	
+	if ((vector_size(expr_pops, op_stack) == 0) && (has_level != -1)) {
+		vector_del(expr_pops, op_stack);
+		return e;
+	}
+	printf("Error: invalid expression: unexpected token\n");
+	proc_token_print(tok);
+	proc_token_del(tok);
+failed:
+	vector_del(expr_pops, op_stack);
+	if (e) expr_del(e);
+	return NULL;
+}
+static int eval_expression(expr_t *e, khash_t(const_map) *const_map, num_constant_t *dest) {
+	// Evaluate the expression (we suppose it is constant)
+	switch (e->typ) {
+	case ETY_VAR: {
+		khiter_t it = kh_get(const_map, const_map, string_content(e->val.var));
+		if (it != kh_end(const_map)) {
+			*dest = kh_val(const_map, it);
+			return 1;
+		}
+		printf("Error: failed to evaluate expression: expression is not constant (variable)\n");
+		return 0; }
+		
+	case ETY_CONST:
+		*dest = e->val.cst;
+		return 1;
+		
+	// case ETY_GENERIC:
+		
+	case ETY_CALL:
+		printf("Error: failed to evaluate expression: expression is not constant (function call)\n");
+		return 0;
+		
+	case ETY_ACCESS:
+	case ETY_PTRACCESS:
+		printf("Error: failed to evaluate expression: expression is not constant (member access)\n");
+		return 0;
+		
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+	case ETY_UNARY:
+		if (!eval_expression(e->val.unary.e, const_map, dest)) return 0;
+		
+		switch (e->val.unary.typ) {
+		case UOT_POSTINCR:
+		case UOT_POSTDECR:
+		case UOT_PREINCR:
+		case UOT_PREDECR:
+		case UOT_REF:
+		case UOT_DEREF:
+			printf("Error: failed to evaluate expression: expression is not constant (assignment or memory accesses)\n");
+			return 0;
+		case UOT_POS:
+			return 1; // Nothing to do
+		case UOT_NEG:
+			switch (dest->typ) {
+			case NCT_FLOAT:   dest->val.f   = -dest->val.f;   return 1;
+			case NCT_DOUBLE:  dest->val.d   = -dest->val.d;   return 1;
+			case NCT_LDOUBLE: dest->val.l   = -dest->val.l;   return 1;
+			case NCT_INT32:   dest->val.i32 = -dest->val.i32; return 1;
+			case NCT_UINT32:  dest->val.u32 = -dest->val.u32; return 1;
+			case NCT_INT64:   dest->val.i64 = -dest->val.i64; return 1;
+			case NCT_UINT64:  dest->val.u64 = -dest->val.u64; return 1;
+			default: return 0;
+			}
+		case UOT_ANOT:
+			switch (dest->typ) {
+			case NCT_FLOAT:
+			case NCT_DOUBLE:
+			case NCT_LDOUBLE:
+				printf("Error: failed to evaluate expression: cannot bitwise-negate a floating point number\n");
+				return 0;
+			case NCT_INT32:  dest->val.i32 = ~dest->val.i32; return 1;
+			case NCT_UINT32: dest->val.u32 = ~dest->val.u32; return 1;
+			case NCT_INT64:  dest->val.i64 = ~dest->val.i64; return 1;
+			case NCT_UINT64: dest->val.u64 = ~dest->val.u64; return 1;
+			default: return 0;
+			}
+		case UOT_BNOT:
+			switch (dest->typ) {
+			case NCT_FLOAT:   dest->typ = NCT_INT32; dest->val.i32 = !dest->val.f;   return 1;
+			case NCT_DOUBLE:  dest->typ = NCT_INT32; dest->val.i32 = !dest->val.d;   return 1;
+			case NCT_LDOUBLE: dest->typ = NCT_INT32; dest->val.i32 = !dest->val.l;   return 1;
+			case NCT_INT32:   dest->typ = NCT_INT32; dest->val.i32 = !dest->val.i32; return 1;
+			case NCT_UINT32:  dest->typ = NCT_INT32; dest->val.i32 = !dest->val.u32; return 1;
+			case NCT_INT64:   dest->typ = NCT_INT32; dest->val.i32 = !dest->val.i64; return 1;
+			case NCT_UINT64:  dest->typ = NCT_INT32; dest->val.i32 = !dest->val.u64; return 1;
+			default: return 0;
+			}
+		default: return 0;
+		}
+		
+	case ETY_BINARY: {
+		num_constant_t dest1, dest2;
+		if (!eval_expression(e->val.binary.e1, const_map, &dest1)) return 0;
+		if (!eval_expression(e->val.binary.e2, const_map, &dest2)) return 0;
+		
+		switch (e->val.binary.typ) {
+		case BOT_ASSGN_EQ:
+		case BOT_ASSGN_ADD:
+		case BOT_ASSGN_SUB:
+		case BOT_ASSGN_MUL:
+		case BOT_ASSGN_DIV:
+		case BOT_ASSGN_MOD:
+		case BOT_ASSGN_LSH:
+		case BOT_ASSGN_RSH:
+		case BOT_ASSGN_AAND:
+		case BOT_ASSGN_AXOR:
+		case BOT_ASSGN_AOR:
+		case BOT_ARRAY: // Is this possible?
+			printf("Error: failed to evaluate expression: expression is not constant (assignments)\n");
+			return 0;
+#define DOIT(op) \
+			promote_csts(&dest1, &dest2); \
+			switch ((dest->typ = dest1.typ)) { \
+			case NCT_FLOAT:   dest->val.f   = dest1.val.f   op dest2.val.f  ; return 1; \
+			case NCT_DOUBLE:  dest->val.d   = dest1.val.d   op dest2.val.d  ; return 1; \
+			case NCT_LDOUBLE: dest->val.l   = dest1.val.l   op dest2.val.l  ; return 1; \
+			case NCT_INT32:   dest->val.i32 = dest1.val.i32 op dest2.val.i32; return 1; \
+			case NCT_UINT32:  dest->val.u32 = dest1.val.u32 op dest2.val.u32; return 1; \
+			case NCT_INT64:   dest->val.i64 = dest1.val.i64 op dest2.val.i64; return 1; \
+			case NCT_UINT64:  dest->val.u64 = dest1.val.u64 op dest2.val.u64; return 1; \
+			default: return 0;                                                          \
+			}
+#define DOIT_INT(op) \
+			promote_csts(&dest1, &dest2); \
+			switch ((dest->typ = dest1.typ)) { \
+			case NCT_FLOAT: \
+			case NCT_DOUBLE: \
+			case NCT_LDOUBLE: \
+				printf("Error: failed to evaluate expression: binary operation %u incompatible with floating point numbers\n", e->val.binary.typ); \
+				return 0; \
+			case NCT_INT32:   dest->val.i32 = dest1.val.i32 op dest2.val.i32; return 1; \
+			case NCT_UINT32:  dest->val.u32 = dest1.val.u32 op dest2.val.u32; return 1; \
+			case NCT_INT64:   dest->val.i64 = dest1.val.i64 op dest2.val.i64; return 1; \
+			case NCT_UINT64:  dest->val.u64 = dest1.val.u64 op dest2.val.u64; return 1; \
+			default: return 0;                                                          \
+			}
+#define DOIT_BOOL(op) \
+			promote_csts(&dest1, &dest2); \
+			switch (dest1.typ) { \
+			case NCT_FLOAT:   dest->typ = NCT_INT32; dest->val.i32 = dest1.val.f   op dest2.val.f  ; return 1; \
+			case NCT_DOUBLE:  dest->typ = NCT_INT32; dest->val.i32 = dest1.val.d   op dest2.val.d  ; return 1; \
+			case NCT_LDOUBLE: dest->typ = NCT_INT32; dest->val.i32 = dest1.val.l   op dest2.val.l  ; return 1; \
+			case NCT_INT32:   dest->typ = NCT_INT32; dest->val.i32 = dest1.val.i32 op dest2.val.i32; return 1; \
+			case NCT_UINT32:  dest->typ = NCT_INT32; dest->val.i32 = dest1.val.u32 op dest2.val.u32; return 1; \
+			case NCT_INT64:   dest->typ = NCT_INT32; dest->val.i32 = dest1.val.i64 op dest2.val.i64; return 1; \
+			case NCT_UINT64:  dest->typ = NCT_INT32; dest->val.i32 = dest1.val.u64 op dest2.val.u64; return 1; \
+			default: return 0;                                                                                 \
+			}
+		case BOT_ADD: DOIT(+)
+		case BOT_SUB: DOIT(-)
+		case BOT_MUL: DOIT(*)
+		case BOT_DIV: DOIT(/)
+		case BOT_MOD: DOIT_INT(%)
+		case BOT_LSH: DOIT_INT(<<)
+		case BOT_RSH: DOIT_INT(>>)
+		case BOT_LT: DOIT_BOOL(<)
+		case BOT_GT: DOIT_BOOL(>)
+		case BOT_LE: DOIT_BOOL(<=)
+		case BOT_GE: DOIT_BOOL(>=)
+		case BOT_EQ: DOIT_BOOL(==)
+		case BOT_NE: DOIT_BOOL(!=)
+		case BOT_AAND: DOIT_INT(&)
+		case BOT_AXOR: DOIT_INT(^)
+		case BOT_AOR: DOIT_INT(|)
+		case BOT_BAND: DOIT(&&)
+		case BOT_BOR: DOIT(||)
+		case BOT_COMMA: *dest = dest2; return 1;
+#undef DOIT_BOOL
+#undef DOIT_INT
+#undef DOIT
+		default: return 0;
+		} }
+		
+	case ETY_TERNARY: {
+		num_constant_t dest1, dest2, dest3;
+		if (!eval_expression(e->val.ternary.e1, const_map, &dest1)) return 0;
+		if (!eval_expression(e->val.ternary.e2, const_map, &dest2)) return 0;
+		if (!eval_expression(e->val.ternary.e3, const_map, &dest3)) return 0;
+		
+		switch (e->val.ternary.typ) {
+		case TOT_COND:
+			promote_csts(&dest2, &dest3);
+			switch (dest1.typ) {
+			case NCT_FLOAT:   *dest = dest1.val.f   ? dest2 : dest3; return 1;
+			case NCT_DOUBLE:  *dest = dest1.val.d   ? dest2 : dest3; return 1;
+			case NCT_LDOUBLE: *dest = dest1.val.l   ? dest2 : dest3; return 1;
+			case NCT_INT32:   *dest = dest1.val.i32 ? dest2 : dest3; return 1;
+			case NCT_UINT32:  *dest = dest1.val.u32 ? dest2 : dest3; return 1;
+			case NCT_INT64:   *dest = dest1.val.i64 ? dest2 : dest3; return 1;
+			case NCT_UINT64:  *dest = dest1.val.u64 ? dest2 : dest3; return 1;
+			default: return 0;
+			}
+		default: return 0;
+		} }
+#pragma GCC diagnostic pop
+		
+	// case ETY_INIT_LIST:
+		
+	case ETY_CAST:
+		if (!eval_expression(e->val.cast.e, const_map, dest)) return 0;
+		
+		if (e->val.cast.typ->typ == TYPE_BUILTIN) {
+			switch (e->val.cast.typ->val.builtin) {
+			case BTT_INT:
+				switch (dest->typ) {
+				case NCT_FLOAT:   dest->val.i32 = (int32_t)dest->val.f; break;
+				case NCT_DOUBLE:  dest->val.i32 = (int32_t)dest->val.d; break;
+				case NCT_LDOUBLE: dest->val.i32 = (int32_t)dest->val.l; break;
+				case NCT_INT32: break;
+				case NCT_UINT32:  dest->val.i32 = (int32_t)dest->val.u32; break;
+				case NCT_INT64:   dest->val.i32 = (int32_t)dest->val.i64; break;
+				case NCT_UINT64:  dest->val.i32 = (int32_t)dest->val.u64; break;
+				}
+				dest->typ = NCT_INT32;
+				return 1;
+			case BTT_VOID:
+			case BTT_BOOL:
+			case BTT_CHAR:
+			case BTT_SCHAR:
+			case BTT_UCHAR:
+			case BTT_SHORT:
+			case BTT_SSHORT:
+			case BTT_USHORT:
+			case BTT_SINT:
+			case BTT_UINT:
+			case BTT_LONG:
+			case BTT_SLONG:
+			case BTT_ULONG:
+			case BTT_LONGLONG:
+			case BTT_SLONGLONG:
+			case BTT_ULONGLONG:
+			case BTT_INT128:
+			case BTT_SINT128:
+			case BTT_UINT128:
+			case BTT_S8:
+			case BTT_U8:
+			case BTT_S16:
+			case BTT_U16:
+			case BTT_S32:
+			case BTT_U32:
+			case BTT_S64:
+			case BTT_U64:
+			case BTT_FLOAT:
+			case BTT_CFLOAT:
+			case BTT_IFLOAT:
+			case BTT_DOUBLE:
+			case BTT_CDOUBLE:
+			case BTT_IDOUBLE:
+			case BTT_LONGDOUBLE:
+			case BTT_CLONGDOUBLE:
+			case BTT_ILONGDOUBLE:
+			case BTT_VA_LIST:
+			default:
+				printf("Error: TODO: cast to builtin in constant expression\n");
+				return 0;
+			}
+		} else {
+			printf("Error: cast in constant expression\n");
+			return 0;
+		}
+		
+	default: return 0;
+	}
+}
+
+// declaration-specifier with storage != NULL
+// specifier-qualifier-list + static_assert-declaration with storage == NULL
+static int parse_declaration_specifier(khash_t(struct_map) *struct_map, khash_t(type_map) *type_map, khash_t(type_map) *enum_map,
+        type_t *(*builtins)[LAST_BUILTIN + 1], khash_t(const_map) *const_map,
+        khash_t(type_set) *type_set, preproc_t *prep, proc_token_t *tok, enum decl_storage *storage, enum decl_spec *spec, type_t *typ) {
+	if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_STATIC_ASSERT)) {
+		// _Static_assert ( constant-expression , string-literal ) ;
+		// Empty destructor
+		*tok = proc_next_token(prep);
+		if ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != SYM_LPAREN)) {
+			printf("Error: unexpected token in static assertion declaration\n");
+			proc_token_del(tok);
+			goto failed;
+		}
+		// Empty destructor
+		*tok = proc_next_token(prep);
+		expr_t *e = parse_expression(struct_map, type_map, enum_map, builtins, const_map, type_set, prep, tok, 14);
+		if (!e) {
+			goto failed;
+		}
+		if ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != SYM_COMMA)) {
+			printf("Error: unexpected token in static assertion expression\n");
+			expr_del(e);
+			proc_token_del(tok);
+			goto failed;
+		}
+		num_constant_t eval;
+		if (!eval_expression(e, const_map, &eval)) {
+			expr_del(e);
+			// Empty destructor
+			goto failed;
+		}
+		expr_del(e);
+		*tok = proc_next_token(prep);
+		if ((tok->tokt != PTOK_STRING) || !tok->tokv.sisstr) {
+			printf("Error: unexpected token in static assertion message\n");
+			proc_token_del(tok);
+			goto failed;
+		}
+		string_t *errmsg = tok->tokv.str;
+		*tok = proc_next_token(prep);
+		if ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != SYM_RPAREN)) {
+			printf("Error: unexpected token in static assertion message\n");
+			proc_token_del(tok);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		if ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != SYM_SEMICOLON)) {
+			printf("Error: unexpected token after static assertion\n");
+			proc_token_del(tok);
+			goto failed;
+		}
+		
+		int iserror;
+		switch (eval.typ) {
+		case NCT_FLOAT:   iserror = (int)eval.val.f == 0; break;
+		case NCT_DOUBLE:  iserror = (int)eval.val.d == 0; break;
+		case NCT_LDOUBLE: iserror = (int)eval.val.l == 0; break;
+		case NCT_INT32:   iserror = eval.val.i32    == 0; break;
+		case NCT_UINT32:  iserror = eval.val.u32    == 0; break;
+		case NCT_INT64:   iserror = eval.val.i64    == 0; break;
+		case NCT_UINT64:  iserror = eval.val.u64    == 0; break;
+		default: iserror = 1;
+		}
+		if (iserror) {
+			printf("Error: static assertion failed: %s\n", string_content(errmsg));
+			string_del(errmsg);
+			// Empty destructor
+			goto failed;
+		}
+		string_del(errmsg);
+		// Empty destructor
+		return 1;
+	}
+	
+parse_cur_token_decl:
+	if (tok->tokt == PTOK_EOF) {
+		printf("Error: unexpected end of file in declaration\n");
+		proc_token_print(tok);
+		goto failed;
+	}
+	// Storage
+	if (storage && (tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_AUTO)) {
+		if (*storage == STORAGE_NONE) *storage = STORAGE_AUTO;
+		else {
+			printf("Error: unexpected storage class specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if (storage && (tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_EXTERN)) {
+		if (*storage == STORAGE_NONE) *storage = STORAGE_EXTERN;
+		else if (*storage == STORAGE_TLS) *storage = STORAGE_TLS_EXTERN;
+		else {
+			printf("Error: unexpected storage class specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if (storage && (tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_REGISTER)) {
+		if (*storage == STORAGE_NONE) *storage = STORAGE_REG;
+		else {
+			printf("Error: unexpected storage class specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if (storage && (tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_STATIC)) {
+		if (*storage == STORAGE_NONE) *storage = STORAGE_STATIC;
+		else if (*storage == STORAGE_TLS) *storage = STORAGE_TLS_STATIC;
+		else {
+			printf("Error: unexpected storage class specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if (storage && (tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_THREAD_LOCAL)) {
+		if (*storage == STORAGE_NONE) *storage = STORAGE_TLS;
+		else if (*storage == STORAGE_EXTERN) *storage = STORAGE_TLS_EXTERN;
+		else if (*storage == STORAGE_STATIC) *storage = STORAGE_TLS_STATIC;
+		else {
+			printf("Error: unexpected storage class specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if (storage && (tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_TYPEDEF)) {
+		if (*storage == STORAGE_NONE) *storage = STORAGE_TYPEDEF;
+		else {
+			printf("Error: unexpected storage class specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	}
+	
+	// Qualifier
+	if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_ATOMIC)) {
+		// Empty destructor
+		*tok = proc_next_token(prep);
+		if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_LPAREN)) {
+			printf("Error: TODO: _Atomic(type-name)\n");
+			// Empty destructor
+			goto failed;
+		} else {
+			typ->is_atomic = 1;
+			goto parse_cur_token_decl;
+		}
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_CONST)) {
+		typ->is_const = 1;
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_RESTRICT)) {
+		typ->is_restrict = 1;
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_VOLATILE)) {
+		typ->is_volatile = 1;
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	}
+	
+	// Specifier
+#define SPEC(bt,post) \
+		if (*spec == SPEC_NONE) {                                                                   \
+			*spec = SPEC_BUILTIN;                                                                   \
+			typ->typ = TYPE_BUILTIN;                                                                \
+			typ->val.builtin = BTT_ ## bt;                                                          \
+			post                                                                                    \
+		} else {                                                                                    \
+			printf("Error: unexpected type specifier '%s' in declaration\n", kw2str[tok->tokv.kw]); \
+			goto failed;                                                                            \
+		}                                                                                           \
+		*tok = proc_next_token(prep);                                                               \
+		goto parse_cur_token_decl;
+#define SPEC_SIGNED(bt, allow_int) \
+		if ((*spec == SPEC_NONE) || (allow_int && (*spec == SPEC_BUILTIN) && (typ->val.builtin == BTT_INT))) {                    \
+			*spec = (allow_int && (*spec == SPEC_NONE)) ? SPEC_BUILTIN_NOINT : SPEC_BUILTIN;                                      \
+			typ->typ = TYPE_BUILTIN;                                                                                              \
+			typ->val.builtin = BTT_ ## bt;                                                                                        \
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (allow_int && (*spec == SPEC_BUILTIN))) && (typ->val.builtin == BTT_SINT)) { \
+			*spec = allow_int ? *spec : SPEC_BUILTIN;                                                                             \
+			typ->val.builtin = BTT_S ## bt;                                                                                       \
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (allow_int && (*spec == SPEC_BUILTIN))) && (typ->val.builtin == BTT_UINT)) { \
+			*spec = allow_int ? *spec : SPEC_BUILTIN;                                                                             \
+			typ->val.builtin = BTT_U ## bt;                                                                                       \
+		} else {                                                                                                                  \
+			printf("Error: unexpected type specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);                               \
+			goto failed;                                                                                                          \
+		}                                                                                                                         \
+		*tok = proc_next_token(prep);                                                                                             \
+		goto parse_cur_token_decl;
+	if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_BOOL)) {
+		SPEC(BOOL,)
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_COMPLEX)) {
+		if (*spec == SPEC_NONE) {
+			*spec = SPEC_COMPLEX;
+		} else if ((*spec == SPEC_BUILTIN_NOINT) && (typ->val.builtin == BTT_LONG)) {
+			*spec = SPEC_LONGCOMPLEX;
+		} else if ((*spec == SPEC_BUILTIN) && (typ->val.builtin == BTT_FLOAT)) {
+			typ->val.builtin = BTT_CFLOAT;
+		} else if ((*spec == SPEC_BUILTIN) && (typ->val.builtin == BTT_DOUBLE)) {
+			typ->val.builtin = BTT_CDOUBLE;
+		} else if ((*spec == SPEC_BUILTIN) && (typ->val.builtin == BTT_LONGDOUBLE)) {
+			typ->val.builtin = BTT_CLONGDOUBLE;
+		} else {
+			printf("Error: unexpected type specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_IMAGINARY)) {
+		if (*spec == SPEC_NONE) {
+			*spec = SPEC_IMAGINARY;
+		} else if ((*spec == SPEC_BUILTIN_NOINT) && (typ->val.builtin == BTT_LONG)) {
+			*spec = SPEC_LONGIMAGINARY;
+		} else if ((*spec == SPEC_BUILTIN) && (typ->val.builtin == BTT_FLOAT)) {
+			typ->val.builtin = BTT_IFLOAT;
+		} else if ((*spec == SPEC_BUILTIN) && (typ->val.builtin == BTT_DOUBLE)) {
+			typ->val.builtin = BTT_IDOUBLE;
+		} else if ((*spec == SPEC_BUILTIN) && (typ->val.builtin == BTT_LONGDOUBLE)) {
+			typ->val.builtin = BTT_ILONGDOUBLE;
+		} else {
+			printf("Error: unexpected type specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_DOUBLE)) {
+		if (*spec == SPEC_NONE) {
+			*spec = SPEC_BUILTIN;
+			typ->typ = TYPE_BUILTIN;
+			typ->val.builtin = BTT_DOUBLE;
+		} else if (*spec == SPEC_COMPLEX) {
+			*spec = SPEC_BUILTIN;
+			typ->typ = TYPE_BUILTIN;
+			typ->val.builtin = BTT_CDOUBLE;
+		} else if (*spec == SPEC_IMAGINARY) {
+			*spec = SPEC_BUILTIN;
+			typ->typ = TYPE_BUILTIN;
+			typ->val.builtin = BTT_IDOUBLE;
+		} else if (*spec == SPEC_LONGCOMPLEX) {
+			*spec = SPEC_BUILTIN;
+			typ->typ = TYPE_BUILTIN;
+			typ->val.builtin = BTT_CLONGDOUBLE;
+		} else if (*spec == SPEC_LONGIMAGINARY) {
+			*spec = SPEC_BUILTIN;
+			typ->typ = TYPE_BUILTIN;
+			typ->val.builtin = BTT_ILONGDOUBLE;
+		} else if ((*spec == SPEC_BUILTIN_NOINT) && (typ->val.builtin == BTT_LONG)) {
+			*spec = SPEC_BUILTIN;
+			typ->val.builtin = BTT_LONGDOUBLE;
+		} else {
+			printf("Error: unexpected type specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_FLOAT)) {
+		if (*spec == SPEC_NONE) {
+			*spec = SPEC_BUILTIN;
+			typ->typ = TYPE_BUILTIN;
+			typ->val.builtin = BTT_FLOAT;
+		} else if (*spec == SPEC_COMPLEX) {
+			*spec = SPEC_BUILTIN;
+			typ->typ = TYPE_BUILTIN;
+			typ->val.builtin = BTT_CFLOAT;
+		} else if (*spec == SPEC_IMAGINARY) {
+			*spec = SPEC_BUILTIN;
+			typ->typ = TYPE_BUILTIN;
+			typ->val.builtin = BTT_IFLOAT;
+		} else {
+			printf("Error: unexpected type specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_CHAR)) {
+		SPEC_SIGNED(CHAR, 0)
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_INT)) {
+		if (*spec == SPEC_NONE) {
+			*spec = SPEC_BUILTIN;
+			typ->typ = TYPE_BUILTIN;
+			typ->val.builtin = BTT_INT;
+		} else if (*spec == SPEC_BUILTIN_NOINT) {
+			*spec = SPEC_BUILTIN;
+		} else {
+			printf("Error: unexpected type specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_INT128)) {
+		SPEC_SIGNED(INT128, 0)
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_LONG)) {
+		if ((*spec == SPEC_NONE) || ((*spec == SPEC_BUILTIN) && (typ->val.builtin == BTT_INT))) {
+			*spec = (*spec == SPEC_NONE) ? SPEC_BUILTIN_NOINT : SPEC_BUILTIN;
+			typ->typ = TYPE_BUILTIN;
+			typ->val.builtin = BTT_LONG;
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (*spec == SPEC_BUILTIN)) && (typ->val.builtin == BTT_SINT)) {
+			typ->val.builtin = BTT_SLONG;
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (*spec == SPEC_BUILTIN)) && (typ->val.builtin == BTT_UINT)) {
+			typ->val.builtin = BTT_ULONG;
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (*spec == SPEC_BUILTIN)) && (typ->val.builtin == BTT_LONG)) {
+			typ->val.builtin = BTT_LONGLONG;
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (*spec == SPEC_BUILTIN)) && (typ->val.builtin == BTT_ULONG)) {
+			typ->val.builtin = BTT_ULONGLONG;
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (*spec == SPEC_BUILTIN)) && (typ->val.builtin == BTT_SLONG)) {
+			typ->val.builtin = BTT_SLONGLONG;
+		} else if ((*spec == SPEC_BUILTIN) && (typ->val.builtin == BTT_DOUBLE)) {
+			typ->val.builtin = BTT_LONGDOUBLE;
+		} else if ((*spec == SPEC_BUILTIN) && (typ->val.builtin == BTT_CDOUBLE)) {
+			typ->val.builtin = BTT_CLONGDOUBLE;
+		} else if ((*spec == SPEC_BUILTIN) && (typ->val.builtin == BTT_IDOUBLE)) {
+			typ->val.builtin = BTT_ILONGDOUBLE;
+		} else if (*spec == SPEC_COMPLEX) {
+			*spec = SPEC_LONGCOMPLEX;
+		} else if (*spec == SPEC_IMAGINARY) {
+			*spec = SPEC_LONGIMAGINARY;
+		} else {
+			printf("Error: unexpected type specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_SHORT)) {
+		SPEC_SIGNED(SHORT, 1)
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_SIGNED)) {
+		if (*spec == SPEC_NONE) {
+			*spec = SPEC_BUILTIN_NOINT;
+			typ->typ = TYPE_BUILTIN;
+			typ->val.builtin = BTT_SINT;
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (*spec == SPEC_BUILTIN)) && (typ->val.builtin == BTT_CHAR)) {
+			typ->val.builtin = BTT_SCHAR;
+		} else if ((*spec == SPEC_BUILTIN) && (typ->val.builtin == BTT_INT)) {
+			typ->val.builtin = BTT_SINT;
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (*spec == SPEC_BUILTIN)) && (typ->val.builtin == BTT_LONG)) {
+			typ->val.builtin = BTT_SLONG;
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (*spec == SPEC_BUILTIN)) && (typ->val.builtin == BTT_LONGLONG)) {
+			typ->val.builtin = BTT_SLONGLONG;
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (*spec == SPEC_BUILTIN)) && (typ->val.builtin == BTT_SHORT)) {
+			typ->val.builtin = BTT_SSHORT;
+		} else {
+			printf("Error: unexpected type specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_UNSIGNED)) {
+		if (*spec == SPEC_NONE) {
+			*spec = SPEC_BUILTIN_NOINT;
+			typ->typ = TYPE_BUILTIN;
+			typ->val.builtin = BTT_UINT;
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (*spec == SPEC_BUILTIN)) && (typ->val.builtin == BTT_CHAR)) {
+			typ->val.builtin = BTT_UCHAR;
+		} else if ((*spec == SPEC_BUILTIN) && (typ->val.builtin == BTT_INT)) {
+			typ->val.builtin = BTT_UINT;
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (*spec == SPEC_BUILTIN)) && (typ->val.builtin == BTT_LONG)) {
+			typ->val.builtin = BTT_ULONG;
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (*spec == SPEC_BUILTIN)) && (typ->val.builtin == BTT_LONGLONG)) {
+			typ->val.builtin = BTT_ULONGLONG;
+		} else if (((*spec == SPEC_BUILTIN_NOINT) || (*spec == SPEC_BUILTIN)) && (typ->val.builtin == BTT_SHORT)) {
+			typ->val.builtin = BTT_USHORT;
+		} else {
+			printf("Error: unexpected type specifier '%s' in declaration\n", kw2str[tok->tokv.kw]);
+			goto failed;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_VOID)) {
+		SPEC(VOID, typ->is_incomplete = 1;)
+	}
+#undef SPEC
+#undef SPEC_SIGNED
+	if ((tok->tokt == PTOK_IDENT) && (*spec == SPEC_NONE)) {
+		// The ident is the type-specifier
+		khiter_t it = kh_get(type_map, type_map, string_content(tok->tokv.str));
+		if (it == kh_end(type_map)) {
+			printf("Error: invalid type '%s' (ident is not a typedef)\n"
+					"Current state:\n"
+					"  storage: %p/%u\n"
+					"  spec: %u\n"
+					"  type: ", string_content(tok->tokv.str), storage, storage ? *storage : STORAGE_NONE, *spec);
+			type_print(typ);
+			printf("\n");
+			string_del(tok->tokv.str);
+			goto failed;
+		} else {
+			*spec = SPEC_TYPE;
+			if (!type_copy_into(typ, kh_val(type_map, it))) {
+				printf("Failed to duplicate type infos\n");
+				string_del(tok->tokv.str);
+				goto failed;
+			}
+			string_del(tok->tokv.str);
+			*tok = proc_next_token(prep);
+			goto parse_cur_token_decl;
+		}
+	}
+	if ((tok->tokt == PTOK_KEYWORD) && (*spec == SPEC_NONE) && ((tok->tokv.kw == KW_STRUCT) || (tok->tokv.kw == KW_UNION))) {
+		int is_struct = tok->tokv.kw == KW_STRUCT;
+		*spec = SPEC_TYPE;
+		
+		// Empty destructor
+		*tok = proc_next_token(prep);
+		khiter_t it = kh_end(struct_map); // Iterator into the struct_map
+		if (tok->tokt == PTOK_IDENT) {
+			string_t *tag = tok->tokv.str;
+			// Token moved
+			*tok = proc_next_token(prep);
+			
+			int iret;
+			it = kh_put(struct_map, struct_map, string_content(tag), &iret);
+			if (iret < 0) {
+				printf("Error: failed to add structure to struct map\n");
+				proc_token_del(tok);
+				goto failed;
+			} else if (iret == 0) {
+				// Structure already declared or defined
+				if (kh_val(struct_map, it)->is_struct != is_struct) {
+					printf("Error: incoherent struct/union tagging of %s\n", string_content(tag));
+					string_del(tag);
+					proc_token_del(tok);
+					goto failed;
+				}
+				++kh_val(struct_map, it)->nrefs;
+				string_del(tag);
+			} else {
+				kh_val(struct_map, it) = struct_new(is_struct, tag);
+				if (!kh_val(struct_map, it)) {
+					printf("Error: failed to create new structure metadata structure\n");
+					string_del(tag);
+					proc_token_del(tok);
+					goto failed;
+				}
+			}
+			typ->typ = TYPE_STRUCT_UNION;
+			typ->val.st = kh_val(struct_map, it);
+			typ->is_incomplete = !typ->val.st->is_defined;
+		} else {
+			typ->typ = TYPE_STRUCT_UNION;
+			typ->val.st = struct_new(is_struct, NULL);
+			if (!typ->val.st) {
+				printf("Error: failed to create new structure metadata structure\n");
+				proc_token_del(tok);
+				goto failed;
+			}
+			typ->is_incomplete = 1;
+		}
+		if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_LBRACKET)) {
+			if (typ->val.st->is_defined) {
+				printf("Error: TODO: struct redefinition\n"
+						"Current state:\n"
+						"  storage: %p/%u\n"
+						"  spec: %u\n"
+						"  type: ", storage, storage ? *storage : STORAGE_NONE, *spec);
+				type_print(typ);
+				printf("\n");
+				goto failed;
+			}
+			
+			VECTOR(st_members) *members = vector_new(st_members);
+			if (!members) {
+				printf("Failed to create a members vector\n");
+				goto failed;
+			}
+			type_t *typ2 = type_new();
+			if (!typ2) {
+				printf("Failed to create a type info structure\n");
+				vector_del(st_members, members);
+				goto failed;
+			}
+			*tok = proc_next_token(prep);
+			while (!proc_token_isend(tok) && ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != SYM_RBRACKET))) {
+				enum decl_spec spec2 = SPEC_NONE;
+				if (!parse_declaration_specifier(struct_map, type_map, enum_map, builtins, const_map, type_set, prep, tok, NULL, &spec2, typ2)) {
+					vector_del(st_members, members);
+					type_del(typ2);
+					goto failed;
+				}
+				if (spec2 == SPEC_NONE) {
+					// Empty destructor
+					*tok = proc_next_token(prep);
+					continue; // Declaration was an assert, typ2 is unchanged
+				}
+				typ2 = type_try_merge(typ2, type_set);
+				if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_SEMICOLON)) {
+					// A struct-declaration that does not declare an anonymous structure or anonymous union
+					// shall contain a struct-declarator-list.
+					if ((typ2->typ != TYPE_STRUCT_UNION) || typ2->val.st->tag) {
+						printf("Error: missing struct-declarator-list\n");
+						vector_del(st_members, members);
+						type_del(typ2);
+						goto failed;
+					}
+					if (!vector_push(st_members, members, ((st_member_t){.name = NULL, .typ = typ2, .is_bitfield = 0}))) {
+						printf("Error: failed to add anonymous structure member\n");
+						vector_del(st_members, members);
+						type_del(typ2);
+						// Empty destructor
+						goto failed;
+					}
+					typ2 = type_new();
+					if (!typ2) {
+						printf("Failed to create a type info structure\n");
+						vector_del(st_members, members);
+						// Empty destructor
+						goto failed;
+					}
+					// Empty destructor
+					*tok = proc_next_token(prep);
+					continue;
+				}
+				
+				struct parse_declarator_dest_s dest2;
+				dest2.structms.struct_map = struct_map;
+				dest2.structms.type_map = type_map;
+				dest2.structms.enum_map = enum_map;
+				dest2.structms.type_set = type_set;
+				dest2.structms.builtins = builtins;
+				dest2.structms.const_map = const_map;
+				dest2.structms.dest = members;
+				if (!parse_declarator(&dest2, prep, tok, STORAGE_NONE, typ2, 0, 1, 1, 1)) {
+					printf("Error parsing struct-declarator-list\n");
+					vector_del(st_members, members);
+					type_del(typ2);
+					// Token is deleted
+					goto failed;
+				}
+				type_del(typ2);
+				if ((tok->tokt != PTOK_SYM) && (tok->tokv.sym != SYM_SEMICOLON)) {
+					printf("Error parsing struct-declarator-list (invalid next token)\n");
+					vector_del(st_members, members);
+					proc_token_del(tok);
+					goto failed;
+				}
+				typ2 = type_new();
+				if (!typ2) {
+					printf("Failed to create a type info structure\n");
+					vector_del(st_members, members);
+					// Empty destructor
+					goto failed;
+				}
+				*tok = proc_next_token(prep);
+			}
+			type_del(typ2);
+			if ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != SYM_RBRACKET)) {
+				printf("Error parsing struct-declarator-list (invalid next token)\n");
+				vector_del(st_members, members);
+				proc_token_del(tok);
+				goto failed;
+			}
+			
+			typ->is_incomplete = 0;
+			typ->val.st->has_incomplete = 0; // Filled by the validate_type step
+			typ->val.st->nmembers = vector_size(st_members, members);
+			typ->val.st->members = vector_steal(st_members, members);
+			typ->val.st->is_defined = 1;
+			*tok = proc_next_token(prep);
+			goto parse_cur_token_decl;
+		} else {
+			if (it == kh_end(struct_map)) {
+				printf("Error: invalid structure declaration: missing tag and/or definition\n");
+				proc_token_del(tok);
+				goto failed;
+			}
+			goto parse_cur_token_decl;
+		}
+	}
+	if ((tok->tokt == PTOK_KEYWORD) && (*spec == SPEC_NONE) && (tok->tokv.kw == KW_ENUM)) {
+		*spec = SPEC_TYPE;
+		
+		// Empty destructor
+		*tok = proc_next_token(prep);
+		string_t *tag = NULL;
+		if (tok->tokt == PTOK_IDENT) {
+			tag = tok->tokv.str;
+			*tok = proc_next_token(prep);
+		}
+		if ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != SYM_LBRACKET)) {
+			if (!tag) {
+				printf("Error: unexpected token after keyword 'enum'\n");
+				proc_token_del(tok);
+				goto failed;
+			}
+			khiter_t it = kh_get(type_map, enum_map, string_content(tag));
+			if (it == kh_end(enum_map)) {
+				printf("Error: enumeration %s has not been defined yet\n", string_content(tag)); // TODO?
+				string_del(tag);
+				proc_token_del(tok);
+				goto failed;
+			}
+			if (!type_copy_into(typ, kh_val(enum_map, it))) {
+				printf("Failed to duplicate enum type infos\n");
+				string_del(tag);
+				proc_token_del(tok);
+				goto failed;
+			}
+			string_del(tag);
+			goto parse_cur_token_decl;
+		}
+		// We are defining the enum
+		// Try in order:
+		//  If all values are 0 <= . <= UINT32_MAX, BTT_U32
+		//  If any value is negative and all values are <= INT32_MAX, BTT_S32
+		//  If any value is negative and all values are <= INT64_MAX, BTT_S64
+		//  If all values are 0 <= . <= UINT64_MAX, BTT_U64
+		//  Otherwise, error
+		// By default, BTT_U32* and the constant is an NCT_INT32
+		// *The AMD ABI says this should rather be BTT_INT (or BTT_S32)
+		// Note that BTT_S32 only when has_neg is true
+		int has_neg = 0, not_in_i32 = 0;
+		enum type_builtin_e btt = BTT_U32;
+		num_constant_t cst = { .typ = NCT_INT32, .val.i32 = -1 };
+		// Empty destructor
+		*tok = proc_next_token(prep);
+		int iret;
+		while ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != SYM_RBRACKET)) {
+			if (tok->tokt != PTOK_IDENT) {
+				printf("Error: invalid token in enumeration definition\n");
+				if (tag) string_del(tag);
+				proc_token_del(tok);
+				goto failed;
+			}
+			char *ident = string_steal(tok->tokv.str);
+			*tok = proc_next_token(prep);
+			khiter_t it = kh_put(const_map, const_map, ident, &iret);
+			if (iret < 0) {
+				printf("Error: failed to add constant %s to the constants map\n", ident);
+				free(ident);
+				if (tag) string_del(tag);
+				proc_token_del(tok);
+				goto failed;
+			} else if (iret == 0) {
+				printf("Error: constant %s is already in the constants map\n", ident);
+				free(ident);
+				if (tag) string_del(tag);
+				proc_token_del(tok);
+				goto failed;
+			}
+			if ((tok->tokt == PTOK_SYM) && ((tok->tokv.sym == SYM_COMMA) || tok->tokv.sym == SYM_RBRACKET)) {
+				switch (cst.typ) {
+				case NCT_INT32:
+					if (cst.val.i32 == INT32_MAX) {
+						cst.typ = NCT_UINT32;
+						cst.val.u32 = (uint32_t)INT32_MAX + 1;
+					} else ++cst.val.i32;
+					break;
+				case NCT_UINT32:
+					if (cst.val.u32 == UINT32_MAX) {
+						cst.typ = NCT_INT64;
+						cst.val.i64 = (int64_t)UINT32_MAX + 1;
+					} else ++cst.val.u32;
+					break;
+				case NCT_INT64:
+					if (cst.val.i64 == INT64_MAX) {
+						cst.typ = NCT_UINT64;
+						cst.val.u64 = (uint64_t)INT64_MAX + 1;
+					} else ++cst.val.i64;
+					break;
+				case NCT_UINT64:
+					if (cst.val.u64 == UINT64_MAX) {
+						printf("Error: enum constant is too big\n");
+						if (tag) string_del(tag);
+						proc_token_del(tok);
+						goto failed;
+					} else ++cst.val.u64;
+					break;
+				case NCT_FLOAT:
+				case NCT_DOUBLE:
+				case NCT_LDOUBLE:
+				default:
+					printf("Internal error: enum constant is a float/double/ldouble\n");
+					if (tag) string_del(tag);
+					proc_token_del(tok);
+					goto failed;
+				}
+			} else if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_EQ)) {
+				// Empty destructor
+				*tok = proc_next_token(prep);
+				expr_t *e = parse_expression(struct_map, type_map, enum_map, builtins, const_map, type_set, prep, tok, 14);
+				if (!e) {
+					goto failed;
+				}
+				if ((tok->tokt != PTOK_SYM) || ((tok->tokv.sym != SYM_COMMA) && (tok->tokv.sym != SYM_RBRACKET))) {
+					printf("Error: unexpected token during enumeration declaration\n");
+					expr_del(e);
+					if (tag) string_del(tag);
+					proc_token_del(tok);
+					goto failed;
+				}
+				if (!eval_expression(e, const_map, &cst)) {
+					expr_del(e);
+					if (tag) string_del(tag);
+					// Empty destructor
+					goto failed;
+				}
+				expr_del(e);
+			}
+			switch (cst.typ) {
+			case NCT_INT32:
+				if (cst.val.i32 < 0) {
+					has_neg = 1;
+					if (btt == BTT_U64) {
+						printf("Error: enum constant is too big\n");
+						if (tag) string_del(tag);
+						// Empty destructor
+						goto failed;
+					}
+					btt = not_in_i32 ? BTT_S64 : BTT_S32;
+				}
+				break;
+			case NCT_UINT32:
+				if (cst.val.u32 > (uint32_t)INT32_MAX) {
+					not_in_i32 = 1;
+					btt = has_neg ? BTT_S64 : BTT_U32;
+				}
+				break;
+			case NCT_INT64:
+				if (cst.val.i64 < 0) {
+					has_neg = 1;
+					if (cst.val.i64 < (int64_t)INT32_MIN) {
+						not_in_i32 = 1;
+					}
+					if (btt == BTT_U64) {
+						printf("Error: enum constant is too big\n");
+						if (tag) string_del(tag);
+						// Empty destructor
+						goto failed;
+					}
+					btt =
+						not_in_i32 ? BTT_S64 :
+						BTT_S32;
+				}
+				if ((cst.val.i64 > (int64_t)INT32_MAX)) {
+					not_in_i32 = 1;
+					if (has_neg) btt = BTT_S64;
+					else if (((btt == BTT_S32) || (btt == BTT_U32)) && (cst.val.i64 <= (int64_t)UINT32_MAX))
+						btt = BTT_U32;
+					else if (btt != BTT_U64) btt = BTT_S64;
+				}
+				break;
+			case NCT_UINT64:
+				if (cst.val.u64 > (uint32_t)INT32_MAX) {
+					not_in_i32 = 1;
+					if (has_neg && (cst.val.u64 > (uint64_t)INT64_MAX)) {
+						printf("Error: enum constant is too big\n");
+						if (tag) string_del(tag);
+						// Empty destructor
+						goto failed;
+					}
+					btt = (cst.val.u64 > (uint64_t)INT64_MAX) ? BTT_U64 : (has_neg || (cst.val.u64 > (uint64_t)UINT32_MAX)) ? BTT_S64 : BTT_U32;
+				}
+				break;
+			case NCT_FLOAT:
+			case NCT_DOUBLE:
+			case NCT_LDOUBLE:
+			default:
+				printf("Error: invalid floating-point enumeration constant\n");
+				if (tag) string_del(tag);
+				// Empty destructor
+				goto failed;
+			}
+			kh_val(const_map, it) = cst;
+			if (tok->tokv.sym == SYM_COMMA) {
+				*tok = proc_next_token(prep);
+			}
+		}
+		if (tag) {
+			char *ctag = string_steal(tag);
+			khiter_t it = kh_put(type_map, enum_map, ctag, &iret);
+			if (iret < 0) {
+				printf("Error: failed to add enumeration %s to the type map\n", ctag);
+				free(ctag);
+				// Empty destructor
+				goto failed;
+			} else if (iret == 0) {
+				printf("Error: enumeration %s already exists\n", ctag);
+				free(ctag);
+				// Empty destructor
+				goto failed;
+			}
+			type_t *new_typ = type_new();
+			if (!new_typ) {
+				printf("Error: failed to create type info for enumeration %s\n", ctag);
+				free(ctag);
+				kh_del(type_map, enum_map, it);
+				// Empty destructor
+				goto failed;
+			}
+			typ->typ = new_typ->typ = TYPE_ENUM;
+			typ->is_incomplete = new_typ->is_incomplete = 0;
+			typ->val.typ = new_typ->val.typ = (*builtins)[btt];
+			typ->val.typ->nrefs += 2;
+			new_typ = type_try_merge(new_typ, type_set);
+			validate_type(new_typ, builtins); // Assume it returns 1
+			kh_val(enum_map, it) = new_typ;
+		} else {
+			typ->typ = TYPE_ENUM;
+			typ->is_incomplete = 0;
+			typ->val.typ = (*builtins)[btt];
+			++typ->val.typ->nrefs;
+		}
+		*tok = proc_next_token(prep);
+		goto parse_cur_token_decl;
+	}
+	
+	if ((*spec != SPEC_BUILTIN) && (*spec != SPEC_BUILTIN_NOINT) && (*spec != SPEC_TYPE)) goto invalid_token;
+	if ((tok->tokt != PTOK_IDENT) && ((tok->tokt != PTOK_SYM) || ((tok->tokv.sym != SYM_COMMA) && (tok->tokv.sym != SYM_SEMICOLON)
+	                                                              && (tok->tokv.sym != SYM_STAR) && (tok->tokv.sym != SYM_LSQBRACKET)
+	                                                              && (tok->tokv.sym != SYM_LPAREN) && (tok->tokv.sym != SYM_RPAREN)
+	                                                              && (tok->tokv.sym != SYM_COLON))))
+		goto invalid_token;
+	
+	return 1;
+	
+invalid_token:
+	printf("Error: unexpected token (parse_declaration_specifier)\n"
+			"Current state:\n"
+			"  storage: %p/%u\n"
+			"  spec: %u\n"
+			"  type: ", storage, storage ? *storage : STORAGE_NONE, *spec);
+	type_print(typ);
+	printf("\n");
+	proc_token_print(tok);
+	proc_token_del(tok);
+	
+failed:
+	return 0;
+}
+
+static int parse_declarator(struct parse_declarator_dest_s *dest, preproc_t *prep, proc_token_t *tok, enum decl_storage storage, type_t *base_type,
+      int is_init, int is_list, int allow_decl, int allow_abstract) {
+	int has_list = 0, has_ident = 0;
+	// TODO: allow_abstract and 'direct-abstract-declarator(opt) ( parameter-type-list(opt) )'
+	
+	string_t *cur_ident = NULL;
+	type_t *typ = base_type; ++typ->nrefs;
+	type_t *cur_bottom = NULL;
+	VECTOR(size_t) *nptr_stack = vector_new_cap(size_t, 1);
+	if (!nptr_stack) {
+		printf("Failed to parse init_declarator_list (allocate nptr_stack)\n");
+		proc_token_del(tok);
+		goto failed0;
+	}
+	vector_push(size_t, nptr_stack, 0); // Always succeed (size < cap)
+	while (1) {
+		switch (tok->tokt) {
+		case PTOK_IDENT:
+			if (!has_ident) {
+				if (allow_decl) {
+					cur_ident = tok->tokv.str;
+					has_ident = 1;
+					*tok = proc_next_token(prep);
+					break;
+				} else {
+					printf("Error: unexpected identifier: abstract declarator do not contain a name\n"
+					       "Current state:\n"
+					       "  storage: %u\n"
+					       "  type: ", storage);
+					type_print(typ);
+					printf("\n");
+					proc_token_print(tok);
+					proc_token_del(tok);
+					goto failed;
+				}
+			} else {
+				printf("Error: unexpected identifier\n"
+				       "Current state:\n"
+				       "  storage: %u\n"
+				       "  type: ", storage);
+				type_print(typ);
+				printf("\n");
+				proc_token_print(tok);
+				proc_token_del(tok);
+				goto failed;
+			}
+		case PTOK_SYM:
+			if (tok->tokv.sym == SYM_LPAREN) {
+				if (has_ident) {
+					type_t *new_typ;
+					
+					VECTOR(types) *args = vector_new(types);
+					if (!args) {
+						printf("Error: failed to create new type (function argument)\n");
+						// Empty destructor
+						goto failed;
+					}
+					int has_varargs = 0;
+					*tok = proc_next_token(prep);
+					if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_VOID)) {
+						// Empty destructor
+						*tok = proc_next_token(prep);
+						if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) {
+							vector_del(types, args);
+							new_typ = type_new();
+							if (!new_typ) {
+								printf("Error: failed to create new function type\n");
+								// Empty destructor
+								goto failed;
+							}
+							new_typ->typ = TYPE_FUNCTION;
+							new_typ->val.fun.has_varargs = 0;
+							new_typ->val.fun.nargs = 0;
+							new_typ->val.fun.args = NULL;
+							// ret will be set later
+							goto end_fun;
+						}
+						if (!proc_unget_token(prep, tok)) {
+							printf("Error: failed to unget processor token\n");
+							// Empty destructor
+							vector_del(types, args);
+							goto failed;
+						}
+						tok->tokt = PTOK_KEYWORD;
+						tok->tokv.kw = KW_VOID;
+					} else if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) {
+						goto no_arg;
+					}
+					while (1) {
+						if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_VARIADIC)) {
+							has_varargs = 1;
+							// Empty destructor
+							*tok = proc_next_token(prep);
+							if ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != SYM_RPAREN)) {
+								printf("Error: invalid token after function variadic argument\n");
+								proc_token_del(tok);
+								vector_del(types, args);
+								goto failed;
+							}
+							break;
+						} else {
+							type_t *typ2 = type_new();
+							if (!typ2) {
+								printf("Error: failed to create new type (function argument)\n");
+								// Empty destructor
+								vector_del(types, args);
+								goto failed;
+							}
+							enum decl_storage storage2 = STORAGE_NONE;
+							enum decl_spec spec2 = SPEC_NONE;
+							if (!parse_declaration_specifier(PDECL_STRUCT_MAP, PDECL_TYPE_MAP, PDECL_ENUM_MAP, PDECL_BUILTINS,
+							         PDECL_CONST_MAP, PDECL_TYPE_SET, prep, tok, &storage2, &spec2, typ2)) {
+								// Token is deleted
+								vector_del(types, args);
+								type_del(typ2);
+								goto failed;
+							}
+							if (spec2 == SPEC_NONE) {
+								// _Static_assert declaration; empty destructor
+								printf("Invalid _Static_assert declaration\n");
+								vector_del(types, args);
+								type_del(typ2);
+								goto failed;
+							}
+							if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) {
+								// Unnamed argument
+								if (!vector_push(types, args, typ2)) {
+									printf("Error: failed to add argument to argument vector\n");
+									vector_del(types, args);
+									type_del(typ2);
+									// Empty destructor
+									goto failed;
+								}
+								break;
+							} else if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_COMMA)) {
+								// Unnamed argument
+								if (!vector_push(types, args, typ2)) {
+									printf("Error: failed to add argument to argument vector\n");
+									vector_del(types, args);
+									type_del(typ2);
+									// Empty destructor
+									goto failed;
+								}
+								// Empty destructor
+								*tok = proc_next_token(prep);
+								continue;
+							}
+							// FIXME: Storage specifiers are ignored most of the time?
+							struct parse_declarator_dest_s dest2;
+							dest2.argt.dest = NULL;
+							dest2.argt.struct_map = PDECL_STRUCT_MAP;
+							dest2.argt.type_map = PDECL_TYPE_MAP;
+							dest2.argt.enum_map = PDECL_ENUM_MAP;
+							dest2.argt.type_set = PDECL_TYPE_SET;
+							dest2.argt.builtins = PDECL_BUILTINS;
+							dest2.argt.const_map = PDECL_CONST_MAP;
+							if (!parse_declarator(&dest2, prep, tok, STORAGE_NONE, typ2, 0, 0, 1, 1)) {
+								// Token is deleted
+								vector_del(types, args);
+								type_del(typ2);
+								goto failed;
+							}
+							type_del(typ2);
+							if (!dest2.argt.dest) {
+								printf("Internal error: argument type is NULL\n");
+								vector_del(types, args);
+								// Empty destructor
+								goto failed;
+							}
+							if (!vector_push(types, args, dest2.argt.dest)) {
+								printf("Error: failed to add argument to argument vector\n");
+								vector_del(types, args);
+								type_del(dest2.argt.dest);
+								// Empty destructor
+								goto failed;
+							}
+							if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) {
+								break;
+							} else if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_COMMA)) {
+								// Empty destructor
+								*tok = proc_next_token(prep);
+								continue;
+							}
+							printf("Error: invalid token after function argument\n");
+							vector_del(types, args);
+							proc_token_del(tok);
+							goto failed;
+						}
+					}
+				no_arg:
+					new_typ = type_new();
+					if (!new_typ) {
+						printf("Error: failed to create new function type\n");
+						// Empty destructor
+						goto failed;
+					}
+					new_typ->typ = TYPE_FUNCTION;
+					new_typ->val.fun.has_varargs = has_varargs;
+					new_typ->val.fun.nargs = vector_size(types, args) ? vector_size(types, args) : (size_t)-1;
+					new_typ->val.fun.args = vector_steal(types, args);
+					
+				end_fun: // (void)
+					if (cur_bottom) {
+						// cur_bottom is a pointer, an array or a function
+						// We have cur_bottom(old) --> below, we want cur_bottom(old) --> array=cur_bottom(new) --> below
+						new_typ->val.fun.ret =
+							(cur_bottom->typ == TYPE_PTR) ? cur_bottom->val.typ :
+							(cur_bottom->typ == TYPE_ARRAY) ? cur_bottom->val.array.typ :
+							cur_bottom->val.fun.ret;
+						*((cur_bottom->typ == TYPE_PTR) ? &cur_bottom->val.typ :
+						(cur_bottom->typ == TYPE_ARRAY) ? &cur_bottom->val.array.typ : &cur_bottom->val.fun.ret) = new_typ;
+						cur_bottom = new_typ;
+					} else {
+						// We have top(old), we want array=top(new)=cur_bottom(new) --> top(old)
+						new_typ->val.fun.ret = typ;
+						cur_bottom = typ = new_typ;
+					}
+					*tok = proc_next_token(prep);
+					break;
+				} else {
+					if (!vector_push(size_t, nptr_stack, 0)) {
+						printf("Failed to parse init_declarator_list (open parenthesis)\n");
+						// Empty destructor
+						goto failed;
+					}
+					*tok = proc_next_token(prep);
+					break;
+				}
+			} else if (tok->tokv.sym == SYM_STAR) {
+				if (has_ident) {
+					printf("Error: invalid token '*' after identifier in declaration\n");
+					// Empty destructor
+					goto failed;
+				} else {
+					type_t *new_typ = type_new_ptr(typ);
+					if (!new_typ) {
+						printf("Failed to parse init_declarator_list (create new pointer typ)\n");
+						// Empty destructor
+						goto failed;
+					}
+					typ = new_typ;
+					++vector_last(size_t, nptr_stack);
+					*tok = proc_next_token(prep);
+					break;
+				}
+			} else if (tok->tokv.sym == SYM_LSQBRACKET) {
+				if (!has_ident) {
+					if (!allow_abstract) {
+						printf("Error: invalid token '[' before identifier in declaration\n");
+						// Empty destructor
+						goto failed;
+					}
+					has_ident = 1;
+				}
+				// Empty destructor
+				*tok = proc_next_token(prep);
+				// Here we have only two array constructors:
+				//  direct-declaration [ assignment-expression(opt) ]
+				//  direct-declaration [ * ]   (complete VLA)
+				size_t nelems; _Bool is_incomplete;
+				if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_RSQBRACKET)) {
+					// Incomplete VLA
+					nelems = (size_t)-1;
+					is_incomplete = 1;
+				} else if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_STAR)) {
+					// Complete VLA, expecting a ']'
+					nelems = (size_t)-1;
+					is_incomplete = 0;
+					// Empty destructor
+					*tok = proc_next_token(prep);
+					if ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != SYM_RSQBRACKET)) {
+						printf("Error: unexpected token during variable length array declaration\n");
+						proc_token_del(tok);
+						goto failed;
+					}
+				} else {
+					// Constant expression, followed by ']'
+					is_incomplete = 0;
+					expr_t *e = parse_expression(PDECL_STRUCT_MAP, PDECL_TYPE_MAP, PDECL_ENUM_MAP, PDECL_BUILTINS, PDECL_CONST_MAP, PDECL_TYPE_SET, prep, tok, 15);
+					if (!e) {
+						goto failed;
+					}
+					if ((tok->tokt != PTOK_SYM) || (tok->tokv.sym != SYM_RSQBRACKET)) {
+						printf("Error: unexpected token during array declaration\n");
+						expr_del(e);
+						proc_token_del(tok);
+						goto failed;
+					}
+					num_constant_t cst;
+					if (!eval_expression(e, PDECL_CONST_MAP, &cst)) {
+						expr_del(e);
+						// Empty destructor
+						goto failed;
+					}
+					expr_del(e);
+					int is_neg;
+					switch (cst.typ) {
+					case NCT_FLOAT:   is_neg = cst.val.f < 0; nelems = (size_t)cst.val.f; break;
+					case NCT_DOUBLE:  is_neg = cst.val.d < 0; nelems = (size_t)cst.val.d; break;
+					case NCT_LDOUBLE: is_neg = cst.val.l < 0; nelems = (size_t)cst.val.l; break;
+					case NCT_INT32:   is_neg = cst.val.i32 < 0; nelems = (size_t)cst.val.i32; break;
+					case NCT_UINT32:  is_neg = 0; nelems = (size_t)cst.val.u32; break;
+					case NCT_INT64:   is_neg = cst.val.i64 < 0; nelems = (size_t)cst.val.i64; break;
+					case NCT_UINT64:  is_neg = 0; nelems = (size_t)cst.val.u64; break;
+					default: is_neg = 1;
+					}
+					if (is_neg) {
+						printf("Error: the size of an array must be nonnegative");
+						// Empty destructor
+						goto failed;
+					}
+				}
+				// Token is ']'
+				
+				if (cur_bottom) {
+					type_t *tmp = type_new();
+					if (!tmp) {
+						printf("Failed to parse init_declarator_list (create new array type)\n");
+						// Empty destructor
+						goto failed;
+					}
+					tmp->typ = TYPE_ARRAY;
+					tmp->val.array.array_sz = nelems;
+					tmp->is_incomplete = is_incomplete;
+					// cur_bottom is a pointer, an array or a function
+					// We have cur_bottom(old) --> below, we want cur_bottom(old) --> array=cur_bottom(new) --> below
+					tmp->val.array.typ =
+						(cur_bottom->typ == TYPE_PTR) ? cur_bottom->val.typ :
+						(cur_bottom->typ == TYPE_ARRAY) ? cur_bottom->val.array.typ :
+						cur_bottom->val.fun.ret;
+					*((cur_bottom->typ == TYPE_PTR) ? &cur_bottom->val.typ :
+					  (cur_bottom->typ == TYPE_ARRAY) ? &cur_bottom->val.array.typ : &cur_bottom->val.fun.ret) = tmp;
+					cur_bottom = tmp;
+				} else {
+					type_t *new_typ = type_new();
+					if (!new_typ) {
+						printf("Failed to parse init_declarator_list (create new array type)\n");
+						// Empty destructor
+						goto failed;
+					}
+					new_typ->typ = TYPE_ARRAY;
+					new_typ->val.array.array_sz = nelems;
+					new_typ->is_incomplete = is_incomplete;
+					// We have top(old), we want array=top(new)=cur_bottom(new) --> top(old)
+					new_typ->val.array.typ = typ;
+					cur_bottom = typ = new_typ;
+				}
+				*tok = proc_next_token(prep);
+				break;
+			} else if (tok->tokv.sym == SYM_RPAREN) {
+				if (!has_ident) {
+					if (!allow_abstract) {
+						printf("Error: invalid token ')' before identifier in declaration\n");
+						// Empty destructor
+						goto failed;
+					}
+					has_ident = 1;
+				}
+				if (vector_size(size_t, nptr_stack) == 1) {
+					if (!is_init && !is_list) goto rparen_ok_ret;
+					printf("Error: closing unopened parenthesis in declaration\n");
+					// Empty destructor
+					goto failed;
+				}
+				size_t ndecr;
+				if (cur_bottom) {
+					ndecr = vector_last(size_t, nptr_stack);
+				} else {
+					if (vector_last(size_t, nptr_stack)) {
+						cur_bottom = typ;
+						ndecr = vector_last(size_t, nptr_stack) - 1;
+					} else ndecr = 0;
+				}
+				for (size_t i = 0; i < ndecr; ++i) {
+					cur_bottom =
+						(cur_bottom->typ == TYPE_PTR) ? cur_bottom->val.typ :
+						(cur_bottom->typ == TYPE_ARRAY) ? cur_bottom->val.array.typ :
+						cur_bottom->val.fun.ret;
+				}
+				vector_pop(size_t, nptr_stack);
+				*tok = proc_next_token(prep);
+				break;
+			} else if ((is_init && (tok->tokv.sym == SYM_EQ))
+			        || (tok->tokv.sym == SYM_COMMA)
+			        || (tok->tokv.sym == SYM_SEMICOLON)
+			        || (is_init && (tok->tokv.sym == SYM_LBRACKET))) {
+			rparen_ok_ret: // Last function argument
+				if (!allow_abstract && !has_ident) {
+					printf("Error: invalid symbol '%s' before identifier\n", sym2str[tok->tokv.sym]);
+					// Empty destructor
+					goto failed;
+				}
+				if (vector_size(size_t, nptr_stack) != 1) {
+					printf("Error: invalid symbol '%s' (missing parenthesis?)\n", sym2str[tok->tokv.sym]);
+					// Empty destructor
+					goto failed;
+				}
+				
+				// Try to free some redundant types
+				typ = type_try_merge(typ, PDECL_TYPE_SET);
+				
+				int validation = validate_storage_type(storage, PDECL_BUILTINS, typ, tok->tokv.sym);
+				if (!validation) {
+					// Empty destructor
+					goto failed;
+				}
+				
+				if (validation == VALIDATION_FUN) {
+					// Function definition; tok is '{'
+					if (!is_init || !is_list || has_list) {
+						// We are not at the top-level or we have an initialization list
+						printf("Error: invalid function definition\n");
+						// Empty destructor
+						goto failed;
+					}
+					// Note that here, dest is a file_t
+					// No argument in function definition means that the function takes no argument,
+					//  whereas no argument in function declaration means the function takes an unspecified number of arguments
+					if (typ->val.fun.nargs == (size_t)-1) typ->val.fun.nargs = 0;
+					
+					int iret;
+					char *cident = string_steal(cur_ident); cur_ident = NULL;
+					khiter_t it = kh_put(type_map, dest->f->decl_map, cident, &iret);
+					if (iret < 0) {
+						printf("Failed to add function '%s' to the declaration map\n", cident);
+						free(cident);
+						// Empty destructor
+						goto failed;
+					} else if (iret == 0) {
+						printf("Error: function '%s' is already in the declaration map\n", cident);
+						free(cident);
+						// Empty destructor
+						goto failed;
+					}
+					
+					kh_val(dest->f->decl_map, it) = typ;
+					
+					// Skip the function body
+					int nlbraces = 0;
+					do {
+						proc_token_del(tok);
+						*tok = proc_next_token(prep);
+						if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_LBRACKET)) ++nlbraces;
+						else if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_RBRACKET)) {
+							if (nlbraces) --nlbraces;
+							else goto success;
+						}
+					} while (!proc_token_isend(tok));
+					printf("Error: unexpected token in function body\n");
+					goto failed;
+				}
+				
+				if (storage == STORAGE_TYPEDEF) {
+					if (!is_init || !is_list) {
+						// We are not at the top-level (note that storage is set to NONE in function arguments)
+						printf("Error: invalid function definition\n");
+						// Empty destructor
+						goto failed;
+					}
+					// Note that here, dest is a file_t
+					int iret;
+					char *cident = string_steal(cur_ident); cur_ident = NULL;
+					khiter_t it = kh_put(type_map, dest->f->type_map, cident, &iret);
+					if (iret < 0) {
+						printf("Failed to add '%s' to the type map\n", cident);
+						free(cident);
+						// Empty destructor
+						goto failed;
+					} else if (iret == 0) {
+						if (!type_t_equal(typ, kh_val(dest->f->type_map, it))) {
+							printf("Error: '%s' is already in the type map with a different type\n", cident);
+							free(cident);
+							type_del(typ);
+							// Empty destructor
+							goto failed;
+						}
+						// We can safely ignore this since we have typedef-ed the same type
+						free(cident);
+						type_del(typ);
+					} else {
+						kh_val(dest->f->type_map, it) = typ;
+					}
+				} else if ((storage != STORAGE_STATIC) && (storage != STORAGE_TLS_STATIC)) {
+					if (is_init && is_list) {
+						// static variables/functions are not exposed
+						int iret;
+						char *cident = string_steal(cur_ident); cur_ident = NULL;
+						khiter_t it = kh_put(type_map, dest->f->decl_map, cident, &iret);
+						if (iret < 0) {
+							printf("Failed to add '%s' to the declaration map\n", cident);
+							free(cident);
+							// Empty destructor
+							goto failed;
+						} else if (iret == 0) {
+							/* if ((storage == STORAGE_NONE) || !type_t_equal(typ, kh_val(dest->f->decl_map, it))) {
+								printf("Error: '%s' is already in the declaration map (storage=%u)\n", cident, storage);
+								free(cident);
+								type_del(typ);
+								// Empty destructor
+								goto failed;
+							} else */ {
+								printf("Warning: '%s' is already in the declaration map with the same type\n", cident);
+								free(cident);
+								type_del(typ);
+							}
+						} else {
+							kh_val(dest->f->decl_map, it) = typ;
+						}
+					} else if (!is_init && !is_list) {
+						dest->argt.dest = typ;
+						if (cur_ident) string_del(cur_ident);
+						goto success;
+					} else if (!is_init && is_list) {
+						if (!vector_push(st_members, dest->structms.dest, ((st_member_t){.name = cur_ident, .typ = typ, .is_bitfield = 0}))) {
+							printf("Error: failed to add structure member %s\n", string_content(cur_ident));
+							string_del(cur_ident);
+							// Empty destructor
+							goto failed;
+						}
+					} else {
+						printf("Internal error: unknown is_init/is_list combination %d%d\n", is_init, is_list);
+						// Empty destructor
+						goto failed;
+					}
+				}
+				if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_EQ)) {
+					// Initialization
+					if (!is_init) {
+						printf("Error: unexpected initializer\n");
+						goto failed;
+					}
+					*tok = proc_next_token(prep);
+					if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_LBRACKET)) {
+						// { ... }
+						int nlbraces = 0;
+						do {
+							proc_token_del(tok);
+							*tok = proc_next_token(prep);
+							if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_LBRACKET)) ++nlbraces;
+							else if ((tok->tokt == PTOK_SYM) && (tok->tokv.sym == SYM_RBRACKET)) {
+								if (nlbraces) --nlbraces;
+								else break;
+							}
+						} while (!proc_token_isend(tok));
+						if (proc_token_isend(tok)) {
+							printf("Error: unexpected token in declaration initializer\n");
+							proc_token_del(tok);
+							goto failed;
+						}
+					} else {
+						expr_t *e = parse_expression(PDECL_STRUCT_MAP, PDECL_TYPE_MAP, PDECL_ENUM_MAP, PDECL_BUILTINS, PDECL_CONST_MAP, PDECL_TYPE_SET, prep, tok, 15);
+						if (!e) {
+							printf("Error: invalid declaration initializer\n");
+							goto failed;
+						}
+						expr_del(e);
+					}
+					if ((tok->tokt != PTOK_SYM) || ((tok->tokv.sym != SYM_COMMA) && (tok->tokv.sym != SYM_SEMICOLON))) {
+						printf("Error: unexpected token in declaration initializer\n");
+						proc_token_del(tok);
+						goto failed;
+					}
+					validation = (tok->tokv.sym == SYM_SEMICOLON) ? VALIDATION_LAST_DECL : VALIDATION_DECL;
+				}
+				if (validation == VALIDATION_LAST_DECL) goto success;
+				else {
+					cur_ident = NULL; has_ident = 0;
+					typ = base_type; ++typ->nrefs;
+					cur_bottom = NULL;
+					vector_last(size_t, nptr_stack) = 0;
+					*tok = proc_next_token(prep);
+					break;
+				}
+			} else if (!is_init && is_list && (tok->tokv.sym == SYM_COLON)) {
+				if (vector_size(size_t, nptr_stack) != 1) {
+					printf("Error: invalid symbol '%s' (missing parenthesis?)\n", sym2str[tok->tokv.sym]);
+					// Empty destructor
+					goto failed;
+				}
+				// Try to free some redundant types
+				typ = type_try_merge(typ, PDECL_TYPE_SET);
+				
+				// storage == STORAGE_NONE
+				*tok = proc_next_token(prep);
+				expr_t *e = parse_expression(dest->structms.struct_map, dest->structms.type_map, dest->structms.enum_map,
+				                             dest->structms.builtins, dest->structms.const_map, dest->structms.type_set,
+				                             prep, tok, 14);
+				if (!e) {
+					goto failed;
+				}
+				if ((tok->tokt != PTOK_SYM) || ((tok->tokv.sym != SYM_COMMA) && (tok->tokv.sym != SYM_SEMICOLON))) {
+					printf("Error: unexpected token in bitfield width\n");
+					expr_del(e);
+					proc_token_del(tok);
+					goto failed;
+				}
+				num_constant_t eval;
+				if (!eval_expression(e, dest->structms.const_map, &eval)) {
+					expr_del(e);
+					// Empty destructor
+					goto failed;
+				}
+				expr_del(e);
+				
+				int validation = validate_storage_type(storage, PDECL_BUILTINS, typ, tok->tokv.sym);
+				if (!validation) {
+					// Empty destructor
+					goto failed;
+				}
+				
+				if (validation == VALIDATION_FUN) {
+					// We are not at the top-level or we have an initialization list
+					// Should never happen
+					printf("Error: invalid function definition in structure definition\n");
+					// Empty destructor
+					goto failed;
+				}
+				
+				size_t width;
+				switch (eval.typ) {
+				case NCT_INT32:
+					if (eval.val.i32 < 0) {
+						printf("Error: invalid negative bitfield width\n");
+						goto failed;
+					}
+					width = (size_t)eval.val.i32;
+					break;
+				case NCT_UINT32:
+					width = (size_t)eval.val.u32;
+					break;
+				case NCT_INT64:
+					if (eval.val.i64 < 0) {
+						printf("Error: invalid negative bitfield width\n");
+						goto failed;
+					}
+					width = (size_t)eval.val.i64;
+					break;
+				case NCT_UINT64:
+					width = (size_t)eval.val.u64;
+					break;
+				case NCT_FLOAT:
+				case NCT_DOUBLE:
+				case NCT_LDOUBLE:
+				default:
+					printf("Error: invalid non-integer bitfield width\n");
+					goto failed;
+				}
+				
+				if (!vector_push(st_members, dest->structms.dest,
+				        ((st_member_t){.name = cur_ident, .typ = typ, .is_bitfield = 1, .bitfield_width = width}))) {
+					printf("Error: failed to add structure member %s\n", string_content(cur_ident));
+					string_del(cur_ident);
+					// Empty destructor
+					goto failed;
+				}
+				if (validation == VALIDATION_LAST_DECL) goto success;
+				else {
+					cur_ident = NULL; has_ident = 0;
+					typ = base_type; ++typ->nrefs;
+					cur_bottom = NULL;
+					vector_last(size_t, nptr_stack) = 0;
+					*tok = proc_next_token(prep);
+					break;
+				}
+			}
+			/* FALLTHROUGH */
+		case PTOK_KEYWORD:
+			if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_ATOMIC)) {
+				if (has_ident) {
+					printf("Error: invalid keyword '_Atomic' after identifier\n");
+					proc_token_print(tok);
+					// Empty destructor
+					goto failed;
+				} else if (!vector_last(size_t, nptr_stack)) {
+					printf("Error: invalid keyword '_Atomic' before symbol '*'\n");
+					proc_token_print(tok);
+					// Empty destructor
+					goto failed;
+				} else {
+					typ->is_atomic = 1;
+					*tok = proc_next_token(prep);
+					break;
+				}
+			} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_CONST)) {
+				if (has_ident) {
+					printf("Error: invalid keyword 'const' after identifier\n");
+					proc_token_print(tok);
+					// Empty destructor
+					goto failed;
+				} else if (!vector_last(size_t, nptr_stack)) {
+					printf("Error: invalid keyword 'const' before symbol '*'\n");
+					proc_token_print(tok);
+					// Empty destructor
+					goto failed;
+				} else {
+					typ->is_const = 1;
+					*tok = proc_next_token(prep);
+					break;
+				}
+			} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_RESTRICT)) {
+				if (has_ident) {
+					printf("Error: invalid keyword 'restrict' after identifier\n");
+					proc_token_print(tok);
+					// Empty destructor
+					goto failed;
+				} else if (!vector_last(size_t, nptr_stack)) {
+					printf("Error: invalid keyword 'restrict' before symbol '*'\n");
+					proc_token_print(tok);
+					// Empty destructor
+					goto failed;
+				} else {
+					typ->is_restrict = 1;
+					*tok = proc_next_token(prep);
+					break;
+				}
+			} else if ((tok->tokt == PTOK_KEYWORD) && (tok->tokv.kw == KW_VOLATILE)) {
+				if (has_ident) {
+					printf("Error: invalid keyword 'volatile' after identifier\n");
+					proc_token_print(tok);
+					// Empty destructor
+					goto failed;
+				} else if (!vector_last(size_t, nptr_stack)) {
+					printf("Error: invalid keyword 'volatile' before symbol '*'\n");
+					proc_token_print(tok);
+					// Empty destructor
+					goto failed;
+				} else {
+					typ->is_volatile = 1;
+					*tok = proc_next_token(prep);
+					break;
+				}
+			}
+			/* FALLTHROUGH */
+		case PTOK_INVALID:
+		case PTOK_NUM:
+		case PTOK_STRING:
+		case PTOK_PRAGMA:
+		case PTOK_EOF:
+			printf("Error: unexpected token (parse_declarator %d%d%d%d)\n"
+			       "Current state:\n"
+			       "  storage: %u\n"
+			       "  type: ", is_init, is_list, allow_decl, allow_abstract, storage);
+			type_print(typ);
+			printf("\n");
+			proc_token_print(tok);
+			proc_token_del(tok);
+			goto failed;
+		}
+	}
+	
+success:
+	vector_del(size_t, nptr_stack);
+	// typ has moved, we must not destroy it
+	return 1;
+failed:
+	vector_del(size_t, nptr_stack);
+failed0:
+	if (cur_ident) string_del(cur_ident);
+	type_del(typ);
+	return 0;
+}
+
+file_t *parse_file(const char *filename, FILE *file) {
+	char *dirname = strchr(filename, '/') ? strndup(filename, (size_t)(strrchr(filename, '/') - filename)) : NULL;
+	preproc_t *prep = preproc_new_file(file, dirname, filename);
+	if (!prep) {
+		printf("Failed to create the preproc structure\n");
+		if (dirname) free(dirname);
+		return NULL;
+	}
+	file_t *ret = file_new();
+	if (!ret) {
+		printf("Failed to create the file structure\n");
+		preproc_del(prep);
+		return NULL;
+	}
+	
+	type_t *typ = type_new();
+	if (!typ) {
+		printf("Failed to create a type info structure\n");
+		goto failed;
+	}
+	while (1) {
+		proc_token_t tok = proc_next_token(prep);
+		if (tok.tokt == PTOK_EOF) {
+			goto success;
+		} else if (tok.tokt == PTOK_PRAGMA) {
+			switch (tok.tokv.pragma.typ) {
+			case PRAGMA_ALLOW_INTS: {
+				const char *typenames[] = {BTT_INT_EXTS};
+				for (size_t i = 0; i < sizeof typenames / sizeof *typenames; ++i) {
+					int iret;
+					char *dup = strdup(typenames[i]);
+					if (!dup) {
+						printf("Failed to create a type info structure\n");
+						goto failed;
+					}
+					type_t *t = ret->builtins[BTT_START_INT_EXT + i];
+					khiter_t it = kh_put(type_map, ret->type_map, dup, &iret);
+					if (iret < 0) {
+						printf("Failed to add an intrinsic to the type map\n");
+						goto failed;
+					} else if (iret == 0) {
+						if (!type_t_equal(t, kh_val(ret->type_map, it))) {
+							printf("Error: %s is already defined\n", dup);
+							free(dup);
+							goto failed;
+						}
+						free(dup);
+					} else {
+						++t->nrefs;
+						kh_val(ret->type_map, it) = t;
+					}
+				}
+				break; }
+			case PRAGMA_MARK_SIMPLE: {
+				khiter_t it = kh_get(type_map, ret->type_map, string_content(tok.tokv.pragma.val));
+				string_del(tok.tokv.pragma.val);
+				if (it == kh_end(ret->type_map)) {
+					printf("Invalid explicit_simple pragma: ident is not a typedef\n");
+					goto failed;
+				}
+				type_t *typ0 = kh_val(ret->type_map, it);
+				if (typ0->typ != TYPE_STRUCT_UNION) {
+					printf("Invalid explicit_simple pragma: ident is not a typedef to a structure or union\n");
+					goto failed;
+				}
+				typ0->val.st->explicit_simple = 1;
+				break; }
+			}
+		} else if (proc_token_iserror(&tok)) {
+			printf("Error: unexpected error token\n");
+			proc_token_del(&tok);
+			goto failed;
+		} else {
+			enum decl_storage storage = STORAGE_NONE;
+			enum decl_spec spec = SPEC_NONE;
+			if (!parse_declaration_specifier(ret->struct_map, ret->type_map, ret->enum_map, &ret->builtins, ret->const_map,
+			                                 ret->type_set, prep, &tok, &storage, &spec, typ)) {
+				goto failed;
+			}
+			if (spec == SPEC_NONE) continue; // Declaration was an assert, typ is unchanged
+			int ok;
+			typ = type_try_merge(typ, ret->type_set);
+			if ((tok.tokt != PTOK_SYM) || (tok.tokv.sym != SYM_SEMICOLON)) {
+			    ok = parse_declarator(&(struct parse_declarator_dest_s){.f = ret}, prep, &tok, storage, typ, 1, 1, 1, 0);
+			} else {
+				ok = validate_storage_type(storage, &ret->builtins, typ, tok.tokv.sym);
+			}
+			if (!ok) {
+				goto failed;
+			} else {
+				// Current token is ';' (or '}' for functions), ie. end of declaration
+				type_del(typ);
+				typ = type_new();
+				if (!typ) {
+					printf("Failed to create a type info structure\n");
+					goto failed;
+				}
+			}
+		}
+	}
+	
+success:
+	preproc_del(prep);
+	type_del(typ);
+	return ret;
+failed:
+	preproc_del(prep);
+	if (typ) type_del(typ);
+	file_del(ret);
+	return NULL;
+}
diff --git a/wrapperhelper/src/parse.h b/wrapperhelper/src/parse.h
new file mode 100644
index 00000000..8c7d54b3
--- /dev/null
+++ b/wrapperhelper/src/parse.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#ifndef PARSE_H
+#define PARSE_H
+
+#include <stdio.h>
+
+#include "lang.h"
+
+void dump_prepare(const char *filename, FILE *file);
+void dump_preproc(const char *filename, FILE *file);
+file_t *parse_file(const char *filename, FILE *file);
+
+#endif // PARSE_H
diff --git a/wrapperhelper/src/prepare.c b/wrapperhelper/src/prepare.c
new file mode 100644
index 00000000..090da0b0
--- /dev/null
+++ b/wrapperhelper/src/prepare.c
@@ -0,0 +1,372 @@
+#include "prepare.h"
+
+#include <string.h>
+
+struct prepare_s {
+	FILE *f;
+	int buf[4];
+	int buf_len; // <= 4 (though 3 *should* be enough)
+	char *srcn;
+	enum prepare_state {
+		PREPST_NONE = 0,
+		PREPST_NL,
+		PREPST_HASH,
+		PREPST_INCL,
+		PREPST_DEF,
+		PREPST_DEFID,
+	} st;
+};
+
+prepare_t *prepare_new_file(FILE *f, const char *filename) {
+	prepare_t *ret = malloc(sizeof *ret);
+	if (!ret) {
+		fclose(f);
+		return NULL;
+	}
+	*ret = (prepare_t){
+		.f = f,
+		.buf = {0, 0, 0},
+		.buf_len = 0,
+		.srcn = strdup(filename),
+		.st = PREPST_NL,
+	};
+	return ret;
+}
+
+void prepare_del(prepare_t *prep) {
+	if (prep->f) fclose(prep->f);
+	if (prep->srcn) free(prep->srcn);
+	free(prep);
+}
+
+static int get_char(prepare_t *src) {
+start_get_char:
+	int c = src->buf_len ? src->buf[--src->buf_len] : getc(src->f);
+	src->buf_len = 0;
+	if (c == '\\') {
+		c = src->buf_len ? src->buf[--src->buf_len] : getc(src->f);
+		if (c == '\n') goto start_get_char;
+		src->buf[src->buf_len++] = c;
+		return '\\';
+	}
+	return c;
+}
+// Do not call this more than twice in a row if the last character retrieved is '\\'
+static void unget_char(prepare_t *src, int c) {
+	src->buf[src->buf_len++] = c;
+}
+
+static void fill_ident(prepare_t *src, string_t *buf) {
+	while (1) {
+		int c = get_char(src);
+		if ((c == '_') || ((c >= '0') && (c <= '9')) || ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'))) {
+			string_add_char(buf, (char)c);
+		} else {
+			unget_char(src, c);
+			return;
+		}
+	}
+}
+
+static void fill_num(prepare_t *src, string_t *buf) {
+	int started_exp = 0;
+	while (1) {
+		int c = get_char(src);
+		if ((c == '_') || (c == '.') || ((c >= '0') && (c <= '9')) || ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'))
+		 || (started_exp && ((c == '+') || (c == '-')))) {
+			started_exp = (c == 'e') || (c == 'E') || (c == 'p') || (c == 'P');
+			string_add_char(buf, (char)c);
+		} else {
+			unget_char(src, c);
+			return;
+		}
+	}
+}
+
+static void fill_str(prepare_t *src, string_t *buf, char end_c, int can_esc) {
+	int has_esc = 0;
+	while (1) {
+		int c = get_char(src);
+		if (has_esc && (c >= 0) && (c <= 0x7F) && (c != '\n')) {
+			// Not technically standard compliant (should support \ooo, \x..., \u..., \U...)
+			// Since we don't really care about parsing the content, only the delimiters, this is good enough
+			string_add_char(buf, '\\');
+			string_add_char(buf, (char)c);
+			has_esc = 0;
+		} else if (c == '\\') {
+			if (can_esc) {
+				has_esc = 1;
+			} else {
+				string_add_char(buf, '\\');
+			}
+		} else if ((c >= 0) && (c <= 0x7F) && (c != end_c)) {
+			has_esc = 0;
+			string_add_char(buf, (char)c);
+		} else {
+			if (has_esc) {
+				// c is invalid or a '\n', or can_esc = 0 and c = end_c
+				string_add_char(buf, '\\');
+			}
+			if (c != end_c)
+				unget_char(src, c);
+			return;
+		}
+	}
+}
+
+#define BASE_NSYMS 25
+static const struct symbs_s {
+	char c;
+	enum token_sym_type_e sym;
+	int nnext;
+	const struct symbs_s *next;
+} *symbs = (struct symbs_s[BASE_NSYMS]){
+#define TERM(ch, t) { .c = ch, .sym = t, .nnext = 0, .next = NULL }
+#define NONTERM(ch, t, n, ...) { .c = ch, .sym = t, .nnext = n, .next = (struct symbs_s[n]){__VA_ARGS__} }
+	// Only '..' must have a sym > LAST_SYM; change next_token if this is not the case
+	NONTERM('.', SYM_DOT, 1, NONTERM('.', LAST_SYM + 1, 1, TERM('.', SYM_VARIADIC))),
+	TERM('{', SYM_LBRACKET),
+	TERM('}', SYM_RBRACKET),
+	TERM('[', SYM_LSQBRACKET),
+	TERM(']', SYM_RSQBRACKET),
+	TERM('(', SYM_LPAREN),
+	TERM(')', SYM_RPAREN),
+	NONTERM('#', SYM_HASH, 1, TERM('#', SYM_HASHHASH)),
+	TERM(';', SYM_SEMICOLON),
+	NONTERM(':', SYM_COLON, 1, TERM(':', SYM_COLONCOLON)),
+	TERM('?', SYM_QUESTION),
+	TERM('~', SYM_TILDE),
+	NONTERM('!', SYM_EXCL, 1, TERM('=', SYM_EXCLEQ)),
+	NONTERM('+', SYM_PLUS, 2, TERM('=', SYM_PLUSEQ), TERM('+', SYM_PLUSPLUS)),
+	NONTERM('-', SYM_DASH, 3, TERM('=', SYM_DASHEQ), TERM('-', SYM_DASHDASH), TERM('>', SYM_DASHGT)),
+	NONTERM('*', SYM_STAR, 1, TERM('=', SYM_STAREQ)),
+	NONTERM('/', SYM_SLASH, 1, TERM('=', SYM_SLASHEQ)),
+	NONTERM('%', SYM_PERCENT, 1, TERM('=', SYM_PERCENTEQ)),
+	NONTERM('^', SYM_HAT, 1, TERM('=', SYM_HATEQ)),
+	NONTERM('&', SYM_AMP, 2, TERM('=', SYM_AMPEQ), TERM('&', SYM_AMPAMP)),
+	NONTERM('|', SYM_PIPE, 2, TERM('=', SYM_PIPEEQ), TERM('|', SYM_PIPEPIPE)),
+	NONTERM('=', SYM_EQ, 1, TERM('=', SYM_EQEQ)),
+	NONTERM('<', SYM_LT, 2, TERM('=', SYM_LTEQ), NONTERM('<', SYM_LTLT, 1, TERM('=', SYM_LTLTEQ))),
+	NONTERM('>', SYM_GT, 2, TERM('=', SYM_GTEQ), NONTERM('>', SYM_GTGT, 1, TERM('=', SYM_GTGTEQ))),
+	TERM(',', SYM_COMMA),
+#undef NONTERM
+#undef TERM
+};
+
+preproc_token_t pre_next_token(prepare_t *src, int allow_comments) {
+start_next_token:
+	int c = get_char(src);
+	if (c == EOF) {
+		if (src->st == PREPST_NL) {
+			return (preproc_token_t){
+				.tokt = PPTOK_EOF,
+				.tokv.c = (char)c
+			};
+		} else {
+			// Force newline at EOF
+			unget_char(src, c);
+			src->st = PREPST_NL;
+			return (preproc_token_t){
+				.tokt = PPTOK_NEWLINE,
+				.tokv.c = (char)c
+			};
+		}
+	}
+	
+	if (src->st == PREPST_INCL && (c == '<')) {
+		src->st = PREPST_NONE;
+		preproc_token_t ret;
+		ret.tokt = PPTOK_INCL;
+		ret.tokv.sisstr = 0;
+		ret.tokv.sstr = string_new();
+		fill_str(src, ret.tokv.sstr, '>', 0);
+		return ret;
+	}
+	if (c == '\'') {
+		src->st = PREPST_NONE;
+		preproc_token_t ret;
+		ret.tokt = PPTOK_STRING;
+		ret.tokv.sisstr = 0;
+		ret.tokv.sstr = string_new_cap(1); // Usually only one character is inside a char literal
+		fill_str(src, ret.tokv.sstr, '\'', 1);
+		return ret;
+	}
+	if (c == '"') {
+		preproc_token_t ret;
+		ret.tokt = (src->st == PREPST_INCL) ? PPTOK_INCL : PPTOK_STRING;
+		src->st = PREPST_NONE;
+		ret.tokv.sisstr = 1;
+		ret.tokv.sstr = string_new();
+		fill_str(src, ret.tokv.sstr, '"', ret.tokt == PPTOK_STRING);
+		return ret;
+	}
+	if ((c == ' ') || (c == '\f') || (c == '\t') || (c == '\v')) {
+		if (src->st == PREPST_DEFID) {
+			src->st = PREPST_NONE;
+			return (preproc_token_t){
+				.tokt = PPTOK_BLANK,
+				.tokv.c = (char)c
+			};
+		} else goto start_next_token;
+	}
+	if (c == '\n') {
+		src->st = PREPST_NL;
+		return (preproc_token_t){
+			.tokt = PPTOK_NEWLINE,
+			.tokv.c = (char)c
+		};
+	}
+	if ((c == '_') || ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'))) {
+		preproc_token_t ret;
+		ret.tokt = PPTOK_IDENT;
+		ret.tokv.str = string_new_cap(1);
+		string_add_char(ret.tokv.str, (char)c);
+		fill_ident(src, ret.tokv.str);
+		src->st =
+			((src->st == PREPST_HASH) && (!strcmp(string_content(ret.tokv.str), "include"))) ? PREPST_INCL :
+			((src->st == PREPST_HASH) && (!strcmp(string_content(ret.tokv.str), "include_next"))) ? PREPST_INCL :
+			((src->st == PREPST_HASH) && (!strcmp(string_content(ret.tokv.str), "define"))) ? PREPST_DEF :
+			(src->st == PREPST_DEF) ? PREPST_DEFID :
+			PREPST_NONE;
+		return ret;
+	}
+	if ((c >= '0') && (c <= '9')) {
+		src->st = PREPST_NONE;
+		preproc_token_t ret;
+		ret.tokt = PPTOK_NUM;
+		ret.tokv.str = string_new_cap(1);
+		string_add_char(ret.tokv.str, (char)c);
+		fill_num(src, ret.tokv.str);
+		return ret;
+	}
+	if (c == '.') {
+		c = get_char(src);
+		if ((c >= '0') && (c <= '9')) {
+			src->st = PREPST_NONE;
+			preproc_token_t ret;
+			ret.tokt = PPTOK_NUM;
+			ret.tokv.str = string_new_cap(2);
+			string_add_char(ret.tokv.str, '.');
+			string_add_char(ret.tokv.str, (char)c);
+			fill_num(src, ret.tokv.str);
+			return ret;
+		} else {
+			unget_char(src, c);
+			c = '.';
+		}
+	}
+	if (c == '/') {
+		c = get_char(src);
+		if (c == '/') {
+			if (allow_comments) {
+				src->st = PREPST_NONE;
+				return (preproc_token_t){
+					.tokt = PPTOK_START_LINE_COMMENT,
+					.tokv.c = '/'
+				};
+			}
+			
+			do {
+				c = get_char(src);
+			} while ((c != EOF) && (c != '\n'));
+			if (c != EOF) {
+				if (src->st == PREPST_NL)
+					goto start_next_token;
+				else {
+					src->st = PREPST_NL;
+					return (preproc_token_t){
+						.tokt = PPTOK_NEWLINE,
+						.tokv.c = (char)c
+					};
+				}
+			}
+			
+			src->st = PREPST_NONE;
+			printf("Unfinished comment while preparing %s\n", src->srcn);
+			return (preproc_token_t){
+				.tokt = PPTOK_INVALID,
+				.tokv.c = (char)c
+			};
+		} else if (c == '*') {
+			c = get_char(src);
+			int last_star = 0;
+			while ((c != EOF) && (!last_star || (c != '/'))) {
+				last_star = c == '*';
+				c = get_char(src);
+			}
+			if (c != EOF) goto start_next_token;
+			
+			src->st = PREPST_NONE;
+			printf("Unfinished comment while preparing %s\n", src->srcn);
+			return (preproc_token_t){
+				.tokt = PPTOK_INVALID,
+				.tokv.c = (char)c
+			};
+		} else {
+			unget_char(src, c);
+			c = '/';
+		}
+	}
+	
+	struct symbs_s const *sym = NULL;
+	for (int i = 0; i < BASE_NSYMS; ++i) {
+		if (c == symbs[i].c) {
+			sym = &symbs[i];
+			break;
+		}
+	}
+	if (sym) {
+		while (sym->nnext) {
+			c = get_char(src);
+			int found = 0;
+			for (int i = 0; i < sym->nnext; ++i) {
+				if (c == sym->next[i].c) {
+					found = 1;
+					sym = &sym->next[i];
+					break;
+				}
+			}
+			if (!found) {
+				unget_char(src, c);
+				break;
+			}
+		}
+		if (sym->sym == LAST_SYM + 1) {
+			unget_char(src, sym->c);
+			sym = &symbs[0]; // This is where no check is made (see comment in the definition of symbs)
+		}
+		src->st = ((src->st == PREPST_NL) && (sym->sym == SYM_HASH)) ? PREPST_HASH : PREPST_NONE;
+		return (preproc_token_t){
+			.tokt = PPTOK_SYM,
+			.tokv.sym = sym->sym
+		};
+	}
+	
+	src->st = PREPST_NONE;
+	printf("Invalid character 0x%X (%c) while preparing %s\n", (unsigned)c, (c >= 0x20) && (c < 127) ? c : '?', src->srcn);
+	return (preproc_token_t){
+		.tokt = PPTOK_INVALID,
+		.tokv.c = (char)c
+	};
+}
+preproc_token_t pre_next_newline_token(prepare_t *src) {
+start_next_token:
+	int c = get_char(src);
+	if (c == EOF) {
+		// Force newline at EOF
+		unget_char(src, c);
+		src->st = PREPST_NL;
+		return (preproc_token_t){
+			.tokt = PPTOK_NEWLINE,
+			.tokv.c = (char)c
+		};
+	}
+	if (c == '\n') {
+		src->st = PREPST_NL;
+		return (preproc_token_t){
+			.tokt = PPTOK_NEWLINE,
+			.tokv.c = (char)c
+		};
+	}
+	goto start_next_token;
+}
diff --git a/wrapperhelper/src/prepare.h b/wrapperhelper/src/prepare.h
new file mode 100644
index 00000000..ccb57a5f
--- /dev/null
+++ b/wrapperhelper/src/prepare.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#ifndef PREPARE_H
+#define PREPARE_H
+
+#include <stdio.h>
+
+#include "cstring.h"
+#include "lang.h"
+
+typedef struct prepare_s prepare_t;
+
+prepare_t *prepare_new_file(FILE *f, const char *filename); // Takes ownership of f
+void prepare_del(prepare_t *src);
+preproc_token_t pre_next_token(prepare_t *src, int allow_comments);
+preproc_token_t pre_next_newline_token(prepare_t *src); // In a comment ignore everything until the EOL or EOF
+
+#endif // PREPARE_H
diff --git a/wrapperhelper/src/preproc.c b/wrapperhelper/src/preproc.c
new file mode 100644
index 00000000..ba99f754
--- /dev/null
+++ b/wrapperhelper/src/preproc.c
@@ -0,0 +1,2901 @@
+// I think this file is too big for GCC to handle properly, there are curious false-positive analyzer warnings
+//  that didn't appear before adding preproc_eval
+#include "preproc.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include "cstring.h"
+#include "khash.h"
+#include "prepare.h"
+
+typedef struct mtoken_s {
+	enum mtoken_e {
+		MTOK_TOKEN,
+		MTOK_ARG,
+		MTOK_STRINGIFY,
+		MTOK_CONCAT,
+	} typ;
+	union {
+		preproc_token_t tok;
+		unsigned argid;
+		struct { struct mtoken_s *l, *r; } concat;
+	} val;
+} mtoken_t;
+KHASH_MAP_INIT_STR(argid_map, unsigned)
+void argid_map_del(khash_t(argid_map) *args) {
+	kh_cstr_t str;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+	kh_foreach_key(args, str, free((void*)str))
+#pragma GCC diagnostic pop
+	kh_destroy(argid_map, args);
+}
+static mtoken_t *mtoken_new_token(preproc_token_t tok) {
+	mtoken_t *ret = malloc(sizeof *ret);
+	if (!ret) return NULL;
+	ret->typ = MTOK_TOKEN;
+	ret->val.tok = tok;
+	return ret;
+}
+static mtoken_t *mtoken_new_arg(unsigned argid, int as_string) {
+	mtoken_t *ret = malloc(sizeof *ret);
+	if (!ret) return NULL;
+	ret->typ = as_string ? MTOK_STRINGIFY : MTOK_ARG;
+	ret->val.argid = argid;
+	return ret;
+}
+static mtoken_t *mtoken_new_concat(mtoken_t *l, mtoken_t *r) { // Takes ownership of l and r
+	mtoken_t *ret = malloc(sizeof *ret);
+	if (!ret) return NULL;
+	ret->typ = MTOK_CONCAT;
+	ret->val.concat.l = l;
+	ret->val.concat.r = r;
+	return ret;
+}
+static void mtoken_del(mtoken_t **tok) {
+	switch ((*tok)->typ) {
+	case MTOK_TOKEN:
+		preproc_token_del(&(*tok)->val.tok);
+		free(*tok);
+		return;
+		
+	case MTOK_CONCAT:
+		mtoken_del(&(*tok)->val.concat.l);
+		mtoken_del(&(*tok)->val.concat.r);
+		free(*tok);
+		return;
+		
+	case MTOK_ARG:
+	case MTOK_STRINGIFY:
+		free(*tok);
+		return;
+	}
+}
+
+static inline void print_macro_tok(mtoken_t *m) {
+	switch (m->typ) {
+	case MTOK_TOKEN:
+		printf("token type %u", m->val.tok.tokt);
+		if (m->val.tok.tokt == PPTOK_IDENT) printf(" IDENT '%s'", string_content(m->val.tok.tokv.str));
+		if (m->val.tok.tokt == PPTOK_NUM  ) printf(" NUM %s", string_content(m->val.tok.tokv.str));
+		if (m->val.tok.tokt == PPTOK_SYM) printf(" SYM %s", sym2str[m->val.tok.tokv.sym]);
+		return;
+		
+	case MTOK_ARG:
+		printf("argument %u", m->val.argid);
+		return;
+		
+	case MTOK_CONCAT:
+		printf("concat {");
+		print_macro_tok(m->val.concat.l);
+		printf("} {");
+		print_macro_tok(m->val.concat.r);
+		printf("}");
+		return;
+		
+	case MTOK_STRINGIFY:
+		printf("string argument %u", m->val.argid);
+		return;
+	}
+}
+
+VECTOR_DECLARE_STATIC(mtoken, mtoken_t*)
+VECTOR_IMPL_STATIC(mtoken, mtoken_del)
+
+typedef struct macro_s {
+	int is_funlike;
+	int has_varargs;
+	unsigned nargs;
+	VECTOR(mtoken) *toks;
+} macro_t;
+
+KHASH_MAP_INIT_STR(macros_map, macro_t)
+KHASH_SET_INIT_STR(string_set)
+static void macro_del(macro_t *m) {
+	vector_del(mtoken, m->toks);
+}
+static void macros_map_del(khash_t(macros_map) *args) {
+	kh_cstr_t str;
+	macro_t *it;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+	kh_foreach_key_value_ref(args, str, it, free((void*)str); macro_del(it))
+#pragma GCC diagnostic pop
+	kh_destroy(macros_map, args);
+}
+static void macros_set_del(khash_t(string_set) *strset) {
+	kh_cstr_t str;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+	kh_foreach_key(strset, str, free((void*)str))
+#pragma GCC diagnostic pop
+	kh_destroy(string_set, strset);
+}
+
+typedef struct ppsource_s {
+	enum ppsrc_e {
+		PPSRC_PREPARE = 0,
+		PPSRC_PPTOKEN,
+		PPSRC_PPTOKENS, // Currently, only pushed after macro expansion when the expansion is not empty
+		PPSRC_PTOKEN,
+	} srct;
+	union {
+		struct {
+			prepare_t *st;
+			char *old_dirname;
+			char *old_filename;
+			_Bool was_sys;
+			size_t old_pathno;
+			// Note: char is used for the depths, which gives a max depth of 256 #ifs before the program silently bugs
+			unsigned char entered_next_ok_cond;
+			unsigned char ok_depth, cond_depth;
+		} prep;
+		preproc_token_t pptok;
+		struct {
+			VECTOR(preproc) *toks; // Cannot contain a #if, #else, ... statement, they *must* come from a prepare_t
+			size_t idx;
+		} pptoks;
+		proc_token_t ptok; // Added by proc_unget_token
+	} srcv;
+} ppsource_t;
+static void ppsource_del(ppsource_t *src) {
+	switch (src->srct) {
+	case PPSRC_PREPARE:
+		// st may be NULL if we failed to create the prepare_t* in preproc_new_file()
+		if (src->srcv.prep.st) prepare_del(src->srcv.prep.st);
+		if (src->srcv.prep.old_dirname) free(src->srcv.prep.old_dirname);
+		if (src->srcv.prep.old_filename) free(src->srcv.prep.old_filename);
+		return;
+		
+	case PPSRC_PPTOKEN:
+		preproc_token_del(&src->srcv.pptok);
+		return;
+		
+	case PPSRC_PPTOKENS:
+		// Do not delete the tokens that escaped, only the remaining tokens and the vector
+		vector_del_free_from(preproc, src->srcv.pptoks.toks, src->srcv.pptoks.idx);
+		return;
+		
+	case PPSRC_PTOKEN:
+		proc_token_del(&src->srcv.ptok);
+		return;
+	}
+}
+#define PREPARE_NEW_FILE(f, fn, old_fn, old_dn, was_sys_, old_no) (ppsource_t){ .srct = PPSRC_PREPARE, \
+	.srcv.prep = { .st = prepare_new_file((f), (fn)), .old_dirname = (old_dn), .old_filename = (old_fn), .was_sys = was_sys_, \
+	               .old_pathno = old_no, .entered_next_ok_cond = 0, .ok_depth = 0, .cond_depth = 0 } }
+VECTOR_DECLARE_STATIC(ppsource, ppsource_t)
+VECTOR_IMPL_STATIC(ppsource, ppsource_del)
+
+struct preproc_s {
+	VECTOR(ppsource) *prep;
+	enum preproc_state_e {
+		PPST_NONE,
+		PPST_NL,
+	} st;
+	khash_t(macros_map) *macros_map;
+	khash_t(string_set) *macros_defined, *macros_used;
+	char *dirname;
+	char *cur_file;
+	_Bool is_sys;
+	size_t cur_pathno;
+};
+
+void preproc_del(preproc_t *src) {
+	vector_del(ppsource, src->prep);
+	macros_set_del(src->macros_used);
+	macros_set_del(src->macros_defined);
+	macros_map_del(src->macros_map);
+	if (src->dirname) free(src->dirname);
+	if (src->cur_file) free(src->cur_file);
+	free(src);
+}
+
+static preproc_token_t ppsrc_next_token(preproc_t *src) {
+	switch (vector_last(ppsource, src->prep).srct) {
+	case PPSRC_PREPARE:
+		return pre_next_token(vector_last(ppsource, src->prep).srcv.prep.st, 0);
+		
+	case PPSRC_PPTOKEN: {
+		preproc_token_t ret = vector_last(ppsource, src->prep).srcv.pptok;
+		vector_pop_nodel(ppsource, src->prep);
+		return ret; }
+		
+	case PPSRC_PPTOKENS: {
+		ppsource_t *tmp = &vector_last(ppsource, src->prep);
+		preproc_token_t ret = vector_content(preproc, tmp->srcv.pptoks.toks)[tmp->srcv.pptoks.idx++];
+		if (tmp->srcv.pptoks.idx >= vector_size(preproc, tmp->srcv.pptoks.toks)) {
+			vector_del_freed(preproc, tmp->srcv.pptoks.toks);
+			vector_pop_nodel(ppsource, src->prep);
+		}
+		return ret; }
+		
+	case PPSRC_PTOKEN:
+	default:
+		return (preproc_token_t){ .tokt = PPTOK_INVALID, .tokv.c = 0 };
+	}
+}
+
+preproc_t *preproc_new_file(FILE *f, char *dirname, const char *filename) {
+	preproc_t *ret = malloc(sizeof *ret);
+	if (!ret) {
+		fclose(f);
+		return NULL;
+	}
+	ret->macros_map = kh_init(macros_map);
+	if (!ret->macros_map) {
+		fclose(f);
+		free(ret);
+		return NULL;
+	}
+	ret->macros_defined = kh_init(string_set);
+	if (!ret->macros_defined) {
+		kh_destroy(macros_map, ret->macros_map);
+		fclose(f);
+		free(ret);
+		return NULL;
+	}
+	ret->macros_used = kh_init(string_set);
+	if (!ret->macros_used) {
+		kh_destroy(macros_map, ret->macros_map);
+		kh_destroy(string_set, ret->macros_defined);
+		fclose(f);
+		free(ret);
+		return NULL;
+	}
+	ret->prep = vector_new_cap(ppsource, 1);
+	if (!ret->prep) {
+		kh_destroy(macros_map, ret->macros_map);
+		kh_destroy(string_set, ret->macros_defined);
+		kh_destroy(string_set, ret->macros_used);
+		fclose(f);
+		free(ret);
+		return NULL;
+	}
+	ret->dirname = NULL;
+	// ret can now be deleted by preproc_del
+	if (!vector_push(ppsource, ret->prep, PREPARE_NEW_FILE(f, filename, NULL, NULL, 0, 0))) {
+		preproc_del(ret);
+		return NULL;
+	}
+	if (!vector_last(ppsource, ret->prep).srcv.prep.st) {
+		preproc_del(ret);
+		return NULL;
+	}
+	ret->st = PPST_NL;
+	ret->is_sys = 0;
+	ret->dirname = dirname;
+	ret->cur_file = strdup(filename);
+	ret->cur_pathno = 0;
+	return ret;
+}
+
+const char *incl_paths[] = {
+	"include-fixed",
+	"/usr/lib/gcc/x86_64-pc-linux-gnu/14.2.1/include",
+	"/usr/local/include",
+	"/usr/lib/gcc/x86_64-pc-linux-gnu/14.2.1/include-fixed",
+	"/usr/include",
+	"/usr/include/tirpc",
+};
+static int try_open_dir(preproc_t *src, string_t *filename) {
+	size_t fnlen = string_len(filename);
+	size_t incl_len = src->dirname ? strlen(src->dirname) : 1;
+	char *fn = malloc(incl_len + fnlen + 2);
+	if (!fn) return 0;
+	if (src->dirname) {
+		memcpy(fn, src->dirname, incl_len);
+		fn[incl_len] = '/';
+	} else {
+		fn[0] = '.';
+		fn[1] = '/';
+	}
+	strcpy(fn + incl_len + 1, string_content(filename));
+	FILE *f = fopen(fn, "r");
+	// printf("Trying %s: %p\n", fn, f);
+	int ret;
+	if (f) {
+		char *new_dirname = strchr(fn, '/') ? strndup(fn, (size_t)(strrchr(fn, '/') - fn)) : NULL;
+		ret = vector_push(ppsource, src->prep, PREPARE_NEW_FILE(f, fn, src->cur_file, src->dirname, src->is_sys, src->cur_pathno));
+		if (ret) {
+			src->is_sys = 0;
+			src->cur_file = fn;
+			src->dirname = new_dirname;
+			src->cur_pathno = 0;
+		}
+	} else {
+		free(fn);
+		ret = 0;
+	}
+	return ret;
+}
+static int try_open_sys(preproc_t *src, string_t *filename, size_t array_off) {
+	size_t fnlen = string_len(filename);
+	for (; array_off < sizeof incl_paths / sizeof *incl_paths; ++array_off) {
+		size_t incl_len = strlen(incl_paths[array_off]);
+		char *fn = malloc(incl_len + fnlen + 2);
+		if (!fn) return 0;
+		memcpy(fn, incl_paths[array_off], incl_len);
+		fn[incl_len] = '/';
+		strcpy(fn + incl_len + 1, string_content(filename));
+		FILE *f = fopen(fn, "r");
+		// printf("Trying %s: %p\n", fn, f);
+		if (f) {
+			char *new_dirname = strchr(fn, '/') ? strndup(fn, (size_t)(strrchr(fn, '/') - fn)) : NULL;
+			int ret = vector_push(ppsource, src->prep, PREPARE_NEW_FILE(f, fn, src->cur_file, src->dirname, src->is_sys, src->cur_pathno));
+			if (ret) {
+				src->is_sys = 1;
+				src->cur_file = fn;
+				src->dirname = new_dirname;
+				src->cur_pathno = array_off + 1;
+			}
+			return ret;
+		}
+		free(fn);
+	}
+	return 0;
+}
+
+static void preprocs_del(VECTOR(preproc) **p) {
+	if (!*p) return;
+	vector_del(preproc, *p);
+}
+VECTOR_DECLARE_STATIC(preprocs, VECTOR(preproc)*)
+VECTOR_IMPL_STATIC(preprocs, preprocs_del)
+
+static VECTOR(preproc) *proc_do_expand(const khash_t(macros_map) *macros, const VECTOR(preproc) *toks,
+                                       khash_t(string_set) *solved_macros, khash_t(string_set) *opt_used_macros);
+	// Does not take any ownership, returns a vector with independent ownerships
+	// opt_used_macros is NULL in regular expansion, non-NULL in #if-expansions
+static VECTOR(preproc) *
+	proc_solve_macro(const khash_t(macros_map) *macros, char *mname, const macro_t *m, VECTOR(preprocs) *margs,
+	                                       khash_t(string_set) *solved_macros, khash_t(string_set) *opt_used_macros);
+	// Moves mname to solved_macros or frees mname, returns a vector with independent ownerships
+	// margs may be NULL if m->is_funlike is false
+	// May change margs if m->has_varargs, but takes no ownership
+	// opt_used_macros is NULL in regular expansion, non-NULL in #if-expansions
+
+static VECTOR(preproc) *
+proc_solve_macro(const khash_t(macros_map) *macros, char *mname, const macro_t *m, VECTOR(preprocs) *margs,
+                 khash_t(string_set) *solved_macros, khash_t(string_set) *opt_used_macros) {
+	if (m->is_funlike && !margs) {
+		printf("<internal error: m->is_funlike && !margs>\n");
+		free(mname);
+		return NULL;
+	}
+	if (m->is_funlike
+	 && (m->nargs != vector_size(preprocs, margs)) // General case
+	 && (!m->has_varargs || (m->nargs > vector_size(preprocs, margs))) // Variadics
+	 && (m->nargs || ((vector_size(preprocs, margs) == 1) && (vector_size(preproc, vector_last(preprocs, margs)) == 0)))) { // Zero argument
+		printf("Invalid argument count for macro %s\n", mname);
+		free(mname);
+		return NULL;
+	}
+	if (m->has_varargs) {
+		// Change margs to have the correct __VA_ARGS__ (argument m->nargs)
+		if (m->nargs == vector_size(preprocs, margs)) {
+			// No varargs, so add an empty one
+			VECTOR(preproc) *marg = vector_new(preproc);
+			if (!marg) {
+				printf("Failed to create __VA_ARGS__ while expanding %s\n", mname);
+				free(mname);
+				return NULL;
+			}
+			if (!vector_push(preprocs, margs, marg)) {
+				printf("Failed to add __VA_ARGS__ while expanding %s\n", mname);
+				vector_del(preproc, marg);
+				free(mname);
+				return NULL;
+			}
+		} else if (m->nargs < vector_size(preprocs, margs) - 1) {
+			// Too many arguments, merge them with commas
+			VECTOR(preproc) *mvarg = vector_content(preprocs, margs)[m->nargs];
+			size_t size0 = vector_size(preproc, mvarg);
+			vector_for_from(preprocs, it, margs, m->nargs + 1) {
+				if (!vector_push(preproc, mvarg, ((preproc_token_t){.tokt = PPTOK_SYM, .tokv.sym = SYM_COMMA}))) {
+					printf("Failed to add comma to __VA_ARGS__ while expanding %s\n", mname);
+					vector_pop_nodel_slice(preproc, mvarg, vector_size(preproc, mvarg) - size0);
+					free(mname);
+					return NULL;
+				}
+				if (!vector_push_vec(preproc, mvarg, *it)) {
+					printf("Failed to add extra argument to __VA_ARGS__ while expanding %s\n", mname);
+					vector_pop_nodel_slice(preproc, mvarg, vector_size(preproc, mvarg) - size0);
+					free(mname);
+					return NULL;
+				}
+			}
+		}
+	}
+	// Avoid 0-allocations
+	VECTOR(preproc) **margs2 = calloc(margs ? (vector_size(preprocs, margs) ? vector_size(preprocs, margs) : 1) : 1, sizeof *margs2);
+	if (!margs2) {
+		printf("Memory error while expanding %s\n", mname);
+		free(mname);
+		return NULL;
+	}
+	VECTOR(preproc) *ret = vector_new(preproc);
+	if (!ret) {
+		printf("Memory error while expanding %s\n", mname);
+		free(margs2);
+		free(mname);
+		return NULL;
+	}
+	
+	VECTOR(mtoken) *st = vector_new_cap(mtoken, vector_size(mtoken, m->toks));
+	if (!st) {
+		printf("Memory error while expanding %s\n", mname);
+		vector_del(preproc, ret);
+		free(margs2);
+		free(mname);
+		return NULL;
+	}
+	vector_for_rev(mtoken, mtok, m->toks) {
+		vector_push(mtoken, st, *mtok);
+	}
+	int need_concat = 0, concat_cur = 0;
+	while (vector_size(mtoken, st)) {
+		mtoken_t *mtok = vector_last(mtoken, st);
+		switch (mtok->typ) {
+		case MTOK_CONCAT: {
+			vector_last(mtoken, st) = mtok->val.concat.r;
+			if (!vector_push(mtoken, st, mtok->val.concat.l)) {
+				vector_del(preproc, ret);
+				ret = NULL;
+				goto solve_done;
+			}
+			++need_concat;
+			break; }
+			
+		case MTOK_TOKEN: {
+			int do_add = 1;
+			if (concat_cur == 2) {
+				preproc_token_t *tok1 = &vector_last(preproc, ret); // Guaranteed to exist
+				preproc_token_t *tok2 = &mtok->val.tok;
+				if (((tok1->tokt == PPTOK_IDENT) || (tok1->tokt == PPTOK_IDENT_UNEXP) || (tok1->tokt == PPTOK_NUM))
+				 && ((tok2->tokt == PPTOK_IDENT) || (tok2->tokt == PPTOK_IDENT_UNEXP) || (tok2->tokt == PPTOK_NUM))) {
+					do_add = 0;
+					// TODO: check if concat is possible, what behaviour should be in the case below
+					if (tok1->tokt == PPTOK_IDENT_UNEXP) tok1->tokt = PPTOK_IDENT;
+					if (!string_add_string(tok1->tokv.str, tok2->tokv.str)) {
+						printf("Memory error while expanding %s\n", mname);
+						vector_del(preproc, ret);
+						ret = NULL;
+						goto solve_done;
+					}
+				} else {
+					printf("Warning: unsupported concatenation between token type %u and %u while expanding %s\n", tok1->tokt, tok2->tokt, mname);
+				}
+			}
+			if (do_add) {
+				preproc_token_t tok2;
+				preproc_token_t *tok1 = &mtok->val.tok;
+				switch (tok1->tokt) {
+				case PPTOK_INVALID:
+				case PPTOK_NEWLINE:
+				case PPTOK_BLANK:
+				case PPTOK_START_LINE_COMMENT:
+				case PPTOK_EOF: tok2 = (preproc_token_t){.tokt = tok1->tokt, .tokv.c = tok1->tokv.c}; break;
+				case PPTOK_SYM: tok2 = (preproc_token_t){.tokt = tok1->tokt, .tokv.sym = tok1->tokv.sym}; break;
+				case PPTOK_IDENT:
+				case PPTOK_IDENT_UNEXP:
+				case PPTOK_NUM: {
+					string_t *dup = string_dup(tok1->tokv.str);
+					if (!dup) {
+						printf("Failed to duplicate string while expanding %s\n", mname);
+						vector_del(preproc, ret);
+						ret = NULL;
+						goto solve_done;
+					}
+					tok2 = (preproc_token_t){.tokt = tok1->tokt, .tokv.str = dup};
+					break; }
+				case PPTOK_INCL:
+				case PPTOK_STRING: {
+					string_t *dup = string_dup(tok1->tokv.sstr);
+					if (!dup) {
+						printf("Failed to duplicate string while expanding %s\n", mname);
+						vector_del(preproc, ret);
+						ret = NULL;
+						goto solve_done;
+					}
+					tok2 = (preproc_token_t){.tokt = tok1->tokt, .tokv.sstr = dup, .tokv.sisstr = tok1->tokv.sisstr};
+					break; }
+				}
+				if (!vector_push(preproc, ret, tok2)) {
+					preproc_token_del(&tok2);
+					vector_del(preproc, ret);
+					ret = NULL;
+					goto solve_done;
+				}
+			}
+			vector_pop_nodel(mtoken, st);
+			if (need_concat) {
+				concat_cur = 2;
+				--need_concat;
+			} else concat_cur = 0;
+			break; }
+			
+		case MTOK_ARG: {
+			VECTOR(preproc) *toks_to_add;
+			if (!need_concat && !concat_cur) {
+				if (!margs2[mtok->val.argid]) {
+					margs2[mtok->val.argid] = proc_do_expand(macros, vector_content(preprocs, margs)[mtok->val.argid], solved_macros, opt_used_macros);
+				}
+				toks_to_add = margs2[mtok->val.argid];
+			} else {
+				toks_to_add = vector_content(preprocs, margs)[mtok->val.argid];
+			}
+			size_t tta_start = 0, len = vector_size(preproc, toks_to_add);
+			if (len && (concat_cur == 2)) {
+				preproc_token_t *tok1 = &vector_last(preproc, ret); // Guaranteed to exist
+				preproc_token_t *tok2 = vector_begin(preproc, toks_to_add);
+				if (((tok1->tokt == PPTOK_IDENT) || (tok1->tokt == PPTOK_IDENT_UNEXP) || (tok1->tokt == PPTOK_NUM))
+				 && ((tok2->tokt == PPTOK_IDENT) || (tok2->tokt == PPTOK_IDENT_UNEXP) || (tok2->tokt == PPTOK_NUM))) {
+					tta_start = 1; --len;
+					// TODO: check if concat is possible, what behaviour should be in the case below
+					if ((tok2->tokt == PPTOK_IDENT_UNEXP) && (tok1->tokt == PPTOK_IDENT)) tok1->tokt = PPTOK_IDENT;
+					if (!string_add_string(tok1->tokv.str, tok2->tokv.str)) {
+						printf("Memory error while expanding %s\n", mname);
+						vector_del(preproc, ret);
+						ret = NULL;
+						goto solve_done;
+					}
+				} else {
+					printf("Warning: unsupported concatenation between token type %u and %u while expanding %s\n", tok1->tokt, tok2->tokt, mname);
+				}
+			}
+			if (len) {
+				preproc_token_t tok2;
+				for (preproc_token_t *tok1 = vector_begin(preproc, toks_to_add) + tta_start; tok1 != vector_end(preproc, toks_to_add); ++tok1){
+					switch (tok1->tokt) {
+					case PPTOK_INVALID:
+					case PPTOK_NEWLINE:
+					case PPTOK_BLANK:
+					case PPTOK_START_LINE_COMMENT:
+					case PPTOK_EOF: tok2 = (preproc_token_t){.tokt = tok1->tokt, .tokv.c = tok1->tokv.c}; break;
+					case PPTOK_SYM: tok2 = (preproc_token_t){.tokt = tok1->tokt, .tokv.sym = tok1->tokv.sym}; break;
+					case PPTOK_IDENT:
+					case PPTOK_IDENT_UNEXP:
+					case PPTOK_NUM: {
+						string_t *dup = string_dup(tok1->tokv.str);
+						if (!dup) {
+							printf("Failed to duplicate string while expanding %s\n", mname);
+							vector_del(preproc, ret);
+							ret = NULL;
+							goto solve_done;
+						}
+						tok2 = (preproc_token_t){.tokt = tok1->tokt, .tokv.str = dup};
+						break; }
+					case PPTOK_INCL:
+					case PPTOK_STRING: {
+						string_t *dup = string_dup(tok1->tokv.sstr);
+						if (!dup) {
+							printf("Failed to duplicate string while expanding %s\n", mname);
+							vector_del(preproc, ret);
+							ret = NULL;
+							goto solve_done;
+						}
+						tok2 = (preproc_token_t){.tokt = tok1->tokt, .tokv.sstr = dup, .tokv.sisstr = tok1->tokv.sisstr};
+						break; }
+					}
+					if (!vector_push(preproc, ret, tok2)) {
+						preproc_token_del(&tok2);
+						vector_del(preproc, ret);
+						ret = NULL;
+						goto solve_done;
+					}
+				}
+			}
+			vector_pop_nodel(mtoken, st);
+			if (need_concat) {
+				concat_cur = (vector_size(preproc, toks_to_add) || (concat_cur == 2)) ? 2 : 1;
+				--need_concat;
+			} else concat_cur = 0;
+			break; }
+			
+		case MTOK_STRINGIFY:
+			// TODO: better stringifier
+			if (concat_cur == 2) {
+				printf("Warning: unsupported concatenation with strings while expanding %s\n", mname);
+			}
+			preproc_token_t tok2;
+			{
+				string_t *dup = string_new_cap(15);
+				if (!dup) {
+					printf("Failed to duplicate string while expanding %s\n", mname);
+					vector_del(preproc, ret);
+					ret = NULL;
+					goto solve_done;
+				}
+				string_add_char(dup, '<');
+				string_add_char(dup, 'S');
+				string_add_char(dup, 't');
+				string_add_char(dup, 'r');
+				string_add_char(dup, 'i');
+				string_add_char(dup, 'n');
+				string_add_char(dup, 'g');
+				string_add_char(dup, 'i');
+				string_add_char(dup, 'f');
+				string_add_char(dup, 'y');
+				string_add_char(dup, ' ');
+				string_add_char(dup, 'a');
+				string_add_char(dup, 'r');
+				string_add_char(dup, 'g');
+				string_add_char(dup, '>');
+				tok2 = (preproc_token_t){.tokt = PPTOK_STRING, .tokv.sstr = dup, .tokv.sisstr = 1};
+			}
+			if (!vector_push(preproc, ret, tok2)) {
+				preproc_token_del(&tok2);
+				vector_del(preproc, ret);
+				ret = NULL;
+				goto solve_done;
+			}
+			vector_pop_nodel(mtoken, st);
+			if (need_concat) {
+				concat_cur = 2;
+				--need_concat;
+			} else concat_cur = 0;
+			break;
+		}
+	}
+	
+solve_done:
+	// Don't call the destructors
+	vector_del_freed(mtoken, st);
+	if (margs) {
+		for (size_t i = 0; i < vector_size(preprocs, margs); ++i) {
+			if (margs2[i]) vector_del(preproc, margs2[i]);
+		}
+	}
+	free(margs2);
+	
+	if (ret) {
+		// Success, now expand the result
+		// First add mname to the set of expanded macros
+		int iret;
+		kh_put(string_set, solved_macros, mname, &iret);
+		if (iret < 0) {
+			printf("Memory error while expanding %s\n", mname);
+			vector_del(preproc, ret);
+			free(mname);
+			return NULL;
+		}
+		
+		// Next expand every remaining macros
+		vector_trim(preproc, ret);
+		VECTOR(preproc) *ret2 = proc_do_expand(macros, ret, solved_macros, opt_used_macros);
+		vector_del(preproc, ret);
+		ret = ret2;
+		if (!ret) return NULL; // There was an error, abort
+		
+		// Finally pop mname (in case we are expanding an argument)
+		khiter_t it = kh_get(string_set, solved_macros, mname);
+		if (it == kh_end(solved_macros)) {
+			printf("Unknown error while expanding a macro\n");
+			vector_del(preproc, ret);
+			return NULL;
+		}
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+		free((void*)kh_key(solved_macros, it));
+#pragma GCC diagnostic pop
+		kh_del(string_set, solved_macros, it);
+	}
+	
+	return ret;
+}
+static VECTOR(preproc) *proc_do_expand(const khash_t(macros_map) *macros, const VECTOR(preproc) *toks,
+                                       khash_t(string_set) *solved_macros, khash_t(string_set) *opt_used_macros) {
+	VECTOR(preproc) *toks2 = vector_new_cap(preproc, vector_size(preproc, toks));
+	if (!toks2) return NULL;
+	vector_for(preproc, tok, toks) {
+		preproc_token_t tok2;
+		switch (tok->tokt) {
+		case PPTOK_INVALID:
+		case PPTOK_NEWLINE:
+		case PPTOK_BLANK:
+		case PPTOK_START_LINE_COMMENT:
+		case PPTOK_EOF: tok2 = (preproc_token_t){.tokt = tok->tokt, .tokv.c = tok->tokv.c}; break;
+		case PPTOK_SYM: tok2 = (preproc_token_t){.tokt = tok->tokt, .tokv.sym = tok->tokv.sym}; break;
+		case PPTOK_IDENT:
+		case PPTOK_IDENT_UNEXP:
+		case PPTOK_NUM: {
+			string_t *dup = string_dup(tok->tokv.str);
+			if (!dup) {
+				printf("Failed to duplicate string during full macro expansion\n");
+				vector_del(preproc, toks2);
+				return NULL;
+			}
+			tok2 = (preproc_token_t){.tokt = tok->tokt, .tokv.str = dup};
+			break; }
+		case PPTOK_INCL:
+		case PPTOK_STRING: {
+			string_t *dup = string_dup(tok->tokv.sstr);
+			if (!dup) {
+				printf("Failed to duplicate string during full macro expansion\n");
+				vector_del(preproc, toks2);
+				return NULL;
+			}
+			tok2 = (preproc_token_t){.tokt = tok->tokt, .tokv.sstr = dup, .tokv.sisstr = tok->tokv.sisstr};
+			break; }
+		}
+		vector_push(preproc, toks2, tok2); // cap > size, thus this always succeed
+	}
+	
+	VECTOR(preproc) *ret = vector_new_cap(preproc, vector_size(preproc, toks));
+	if (!ret) return NULL;
+	vector_for(preproc, tok, toks2) {
+		switch (tok->tokt) {
+		case PPTOK_IDENT: {
+			if (opt_used_macros && !strcmp(string_content(tok->tokv.str), "defined") && (vector_end(preproc, toks2) - tok >= 2)) {
+				// We need to support "defined ident" and "defined(ident)"
+				string_t *mname = NULL;
+				preproc_token_t *tok2;
+				// tok2 is the last used token, mname is the ident string
+				if ((tok[1].tokt == PPTOK_SYM) && (tok[1].tokv.sym == SYM_LPAREN)) {
+					if ((vector_end(preproc, toks2) - tok >= 4) && (tok[3].tokt == PPTOK_SYM) && (tok[3].tokv.sym == SYM_RPAREN)) {
+						if ((tok[2].tokt == PPTOK_IDENT) || (tok[2].tokt == PPTOK_IDENT_UNEXP)) {
+							tok2 = tok + 3;
+							mname = tok[2].tokv.str;
+						}
+					}
+				} else if ((tok[1].tokt == PPTOK_IDENT) || (tok[1].tokt == PPTOK_IDENT_UNEXP)) {
+					tok2 = tok + 1;
+					mname = tok[1].tokv.str;
+				}
+				if (mname) {
+					// Add the ident to the used_macros
+					int iret;
+					char *mname2 = strdup(string_content(mname));
+					if (!mname2) {
+						// Abort
+						printf("Failed to add %s to the list of used macros (strdup returned NULL)\n", string_content(mname));
+						goto expand_done;
+					}
+					kh_put(string_set, opt_used_macros, mname2, &iret);
+					if (iret < 0) {
+						// Abort
+						free(mname2);
+						printf("Failed to add %s to the list of used macros (kh_put ireturned %d)\n", string_content(mname), iret);
+						goto expand_done;
+					} else if (iret == 0) {
+						// Just free mname2, it was already present
+						free(mname2);
+					}
+					
+					// Value is either 0 or 1 as a PPTOK_NUM, so we need a string with capacity 1
+					string_t *num_str = string_new_cap(1);
+					if (!num_str) {
+						// Abort
+						printf("Failed to create defined() output\n");
+						goto expand_done;
+					}
+					khiter_t it = kh_get(macros_map, macros, string_content(mname));
+					string_add_char(num_str, (it == kh_end(macros)) ? '0' : '1');
+					if (!vector_push(preproc, ret, ((preproc_token_t){.tokt = PPTOK_NUM, .tokv.str = num_str}))) {
+						printf("Failed to add defined() to the output\n");
+						string_del(num_str);
+						goto expand_done;
+					}
+					
+					// Done
+					string_del(tok->tokv.str);
+					string_del(mname);
+					tok = tok2;
+					break;
+				}
+				// If mname == NULL, we simply ignore it and check if there is a macro called 'defined'
+			}
+			khiter_t it = kh_get(string_set, solved_macros, string_content(tok->tokv.str));
+			if (it != kh_end(solved_macros)) {
+				tok->tokt = PPTOK_IDENT_UNEXP;
+			} else {
+				it = kh_get(macros_map, macros, string_content(tok->tokv.str));
+				if (it != kh_end(macros)) {
+					macro_t *m = &kh_val(macros, it);
+					if (m->is_funlike) {
+						preproc_token_t *tok2 = tok + 1;
+						if ((tok2 < vector_end(preproc, toks2)) && (tok2->tokt == PPTOK_SYM) && (tok2->tokv.sym == SYM_LPAREN)) {
+							unsigned depth = 1;
+							VECTOR(preprocs) *margs = vector_new(preprocs);
+							if (!margs) {
+								printf("Memory error (parsing macro use %s)\n", string_content(tok->tokv.str));
+								if (margs) vector_del(preprocs, margs);
+								goto expand_done;
+							}
+							VECTOR(preproc) *marg = vector_new(preproc);
+							if (!marg) goto gather_args_err_mem;
+							
+							while (depth && (tok2 < vector_end(preproc, toks2) - 1) && (tok2->tokt != PPTOK_EOF) && (tok2->tokt != PPTOK_INVALID)) {
+								++tok2;
+								if ((depth == 1) && (tok2->tokt == PPTOK_SYM) && (tok2->tokv.sym == SYM_COMMA)) {
+									// Possible optimization: emplace NULL if vector_size(marg) == 0
+									// This would avoid allocating a new vector, but needs support in proc_solve_macro
+									vector_trim(preproc, marg);
+									if (!vector_push(preprocs, margs, marg)) goto gather_args_err_mem;
+									marg = vector_new(preproc);
+									if (!marg) goto gather_args_err_mem;
+								} else {
+									if (!vector_push(preproc, marg, *tok2)) goto gather_args_err_mem;
+								}
+								if ((tok2->tokt == PPTOK_SYM) && (tok2->tokv.sym == SYM_LPAREN)) ++depth;
+								else if ((tok2->tokt == PPTOK_SYM) && (tok2->tokv.sym == SYM_RPAREN)) --depth;
+							}
+							if (depth) {
+								// This may be OK ("Unfinished fun-like macro %s\n", string_content(tok->tokv.str));
+								vector_del(preprocs, margs);
+								vector_del(preproc, marg);
+								goto abort_macro;
+							}
+							// margs finishes with a SYM_RPAREN token
+							vector_pop(preproc, marg);
+							vector_trim(preproc, marg);
+							if (!vector_push(preprocs, margs, marg)) goto gather_args_err_mem;
+							marg = NULL;
+							
+							if (0) {
+							gather_args_err_mem:
+								printf("Memory error (parsing macro use %s)\n", string_content(tok->tokv.str));
+								if (marg) vector_del(preproc, marg);
+								vector_del(preprocs, margs);
+								goto expand_done;
+							}
+							
+							// OK, now expand the macro
+							char *mname = string_steal(tok->tokv.str);
+							tok = tok2;
+							
+							VECTOR(preproc) *expanded = proc_solve_macro(macros, mname, m, margs, solved_macros, opt_used_macros);
+							vector_del(preprocs, margs);
+							if (!expanded) {
+								// Error expanding the macro
+								goto expand_done;
+							}
+							if (!vector_push_vec(preproc, ret, expanded)) {
+								printf("Memory error (pushing expanded macro to expanded macro)\n");
+								vector_del(preproc, expanded);
+								goto expand_done;
+							}
+							vector_del_freed(preproc, expanded);
+							// Done
+							break;
+						}
+					} else {
+						VECTOR(preproc) *expanded = proc_solve_macro(macros, string_steal(tok->tokv.str), m, NULL, solved_macros, opt_used_macros);
+						if (!expanded) {
+							++tok; // Current token is already freed (string stolen)
+							goto expand_done;
+						}
+						if (!vector_push_vec(preproc, ret, expanded)) {
+							printf("Failed to extend output for full macro expansion\n");
+							++tok; // Current token is already freed (string stolen)
+							goto expand_done;
+						}
+						vector_del_freed(preproc, expanded);
+						break;
+					}
+				}
+			}
+		}
+			/* FALLTHROUGH */
+		abort_macro:
+		case PPTOK_IDENT_UNEXP:
+		case PPTOK_INVALID:
+		case PPTOK_NUM:
+		case PPTOK_STRING:
+		case PPTOK_INCL:
+		case PPTOK_SYM:
+		case PPTOK_NEWLINE:
+		case PPTOK_BLANK:
+		case PPTOK_START_LINE_COMMENT:
+		case PPTOK_EOF:
+			if (!vector_push(preproc, ret, *tok)) {
+				printf("Failed to duplicate token during full macro expansion\n");
+				goto expand_done;
+			}
+			break;
+		}
+		
+		if (0) {
+		expand_done:
+			vector_del_free_from(preproc, toks2, (size_t)(tok - vector_begin(preproc, toks2)));
+			vector_del(preproc, ret);
+			return NULL;
+		}
+	}
+	vector_del_freed(preproc, toks2);
+	
+	return ret;
+}
+
+#define OPLVL_MUL 2
+#define OPLVL_DIV 2
+#define OPLVL_ADD 3
+#define OPLVL_SUB 3
+#define OPLVL_LSL 4
+#define OPLVL_LSR 4
+#define OPLVL_LET 5
+#define OPLVL_LEE 5
+#define OPLVL_GRT 5
+#define OPLVL_GRE 5
+#define OPLVL_EQU 6
+#define OPLVL_NEQ 6
+#define OPLVL_AAN 7  // Bitwise (Arithmetic)
+#define OPLVL_XOR 8
+#define OPLVL_AOR 9  // Bitwise (Arithmetic)
+#define OPLVL_BAN 10 // Boolean
+#define OPLVL_BOR 11 // Boolean
+#define OPTYP_MUL 1
+#define OPTYP_DIV 2
+#define OPTYP_ADD 1
+#define OPTYP_SUB 2
+#define OPTYP_LSL 1
+#define OPTYP_LSR 2
+#define OPTYP_LET 1
+#define OPTYP_LEE 2
+#define OPTYP_GRT 3
+#define OPTYP_GRE 4
+#define OPTYP_EQU 1
+#define OPTYP_NEQ 2
+#define OPTYP_AAN 1
+#define OPTYP_XOR 1
+#define OPTYP_AOR 1
+#define OPTYP_BAN 1
+#define OPTYP_BOR 1
+typedef struct preproc_eval_aux_s {
+	struct {
+		unsigned st0 : 1;
+		unsigned st2 : 2;
+		unsigned st3 : 2;
+		unsigned st4 : 2;
+		unsigned st5 : 3;
+		unsigned st6 : 2;
+		unsigned st7 : 1;
+		unsigned st8 : 1;
+		unsigned st9 : 1;
+		unsigned st_bool : 1;
+	};
+	int64_t v0;
+	unsigned st1;
+	const preproc_token_t *v1;
+	int64_t v2;
+	int64_t v3;
+	int64_t v4;
+	int64_t v5;
+	int64_t v6;
+	int64_t v7;
+	int64_t v8;
+	int64_t v9;
+	unsigned n_colons; // Number of ':' expected (needs to skip to the end), ie number of ternary where the truth-y branch is taken
+} preproc_eval_aux_t;
+VECTOR_DECLARE_STATIC(ppeaux, preproc_eval_aux_t)
+VECTOR_IMPL_STATIC(ppeaux, (void))
+static int64_t preproc_eval(const VECTOR(preproc) *cond, int *aux_ret) {
+	VECTOR(ppeaux) *stack = vector_new_cap(ppeaux, 1);
+	if (!stack) {
+		printf("Failed to allocate #if evaluation stack vector\n");
+		*aux_ret = 0;
+		return 0;
+	}
+	if (0) {
+	eval_fail:
+		vector_del(ppeaux, stack);
+		*aux_ret = 0;
+		return 0;
+	}
+	vector_push(ppeaux, stack, (preproc_eval_aux_t){0}); // vector_cap >= 1
+	int64_t acc;
+	vector_for(preproc, tok, cond) {
+		if (tok->tokt == PPTOK_NUM) {
+			// Evaluate token as an integer if st0 == 0, error otherwise
+			if (vector_last(ppeaux, stack).st0 == 0) {
+				num_constant_t cst;
+				if (!num_constant_convert(tok->tokv.str, &cst)) {
+					goto eval_fail;
+				}
+				switch (cst.typ) {
+				case NCT_INT32:  acc =          cst.val.i32; break;
+				case NCT_UINT32: acc =          cst.val.u32; break;
+				case NCT_INT64:  acc =          cst.val.i64; break;
+				case NCT_UINT64: acc = (int64_t)cst.val.u64; break;
+				case NCT_FLOAT:
+				case NCT_DOUBLE:
+				case NCT_LDOUBLE:
+				default:
+					printf("Number '%s' is not a valid integer (during #if evaluation)\n", string_content(tok->tokv.str));
+					goto eval_fail;
+				}
+				goto push_acc_to_st0;
+			} else {
+				printf("Invalid number during #if evaluation\n");
+				goto eval_fail;
+			}
+		} else if ((tok->tokt == PPTOK_IDENT) || (tok->tokt == PPTOK_IDENT_UNEXP)) {
+			// Evaluate token as 0 if st0 == 0, error otherwise
+			if (vector_last(ppeaux, stack).st0 == 0) {
+				acc = 0;
+				goto push_acc_to_st0;
+			} else {
+				printf("Invalid ident '%s' during #if evaluation\n", string_content(tok->tokv.str));
+				goto eval_fail;
+			}
+		} else if (tok->tokt == PPTOK_SYM) {
+			int op_lvl;
+			int op_typ;
+			switch (tok->tokv.sym) {
+			case SYM_TILDE:
+				// Unary (st0 == 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					// Unary, prepare lv1 then continue to the next token
+					if (!(vector_last(ppeaux, stack).st1++)) {
+						// Also update v1, since this is the first level 1 operator
+						vector_last(ppeaux, stack).v1 = tok;
+					}
+					goto done_partial_eval;
+				} else {
+					printf("Invalid %snary '%s' in #if expression\n", "u", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				}
+			case SYM_EXCL:
+				// Unary (st0 == 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					// Unary, prepare lv1 then continue to the next token
+					if (!(vector_last(ppeaux, stack).st1++)) {
+						// Also update v1, since this is the first level 1 operator
+						vector_last(ppeaux, stack).v1 = tok;
+					}
+					goto done_partial_eval;
+				} else {
+					printf("Invalid %snary '%s' in #if expression\n", "u", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				}
+			case SYM_PLUS:
+				// May be unary (st0 == 0) or binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					// Unary, prepare lv1 then continue to the next token
+					if (!(vector_last(ppeaux, stack).st1++)) {
+						// Also update v1, since this is the first level 1 operator
+						vector_last(ppeaux, stack).v1 = tok;
+					}
+					goto done_partial_eval;
+				} else {
+					op_lvl = OPLVL_ADD;
+					op_typ = OPTYP_ADD;
+					break;
+				}
+			case SYM_DASH:
+				// May be unary (st0 == 0) or binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					// Unary, prepare lv1 then continue to the next token
+					if (!(vector_last(ppeaux, stack).st1++)) {
+						// Also update v1, since this is the first level 1 operator
+						vector_last(ppeaux, stack).v1 = tok;
+					}
+					goto done_partial_eval;
+				} else {
+					op_lvl = OPLVL_SUB;
+					op_typ = OPTYP_SUB;
+					break;
+				}
+			case SYM_STAR:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_MUL;
+					op_typ = OPTYP_MUL;
+					break;
+				}
+			case SYM_SLASH:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_DIV;
+					op_typ = OPTYP_DIV;
+					break;
+				}
+			case SYM_HAT:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_XOR;
+					op_typ = OPTYP_XOR;
+					break;
+				}
+			case SYM_AMP:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_AAN;
+					op_typ = OPTYP_AAN;
+					break;
+				}
+			case SYM_PIPE:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_AOR;
+					op_typ = OPTYP_AOR;
+					break;
+				}
+			case SYM_EQEQ:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_EQU;
+					op_typ = OPTYP_EQU;
+					break;
+				}
+			case SYM_EXCLEQ:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_NEQ;
+					op_typ = OPTYP_NEQ;
+					break;
+				}
+			case SYM_LT:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_LET;
+					op_typ = OPTYP_LET;
+					break;
+				}
+			case SYM_GT:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_GRT;
+					op_typ = OPTYP_GRT;
+					break;
+				}
+			case SYM_LTEQ:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_LEE;
+					op_typ = OPTYP_LEE;
+					break;
+				}
+			case SYM_GTEQ:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_GRE;
+					op_typ = OPTYP_GRE;
+					break;
+				}
+			case SYM_AMPAMP:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_BAN;
+					op_typ = OPTYP_BAN;
+					break;
+				}
+			case SYM_PIPEPIPE:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_BOR;
+					op_typ = OPTYP_BOR;
+					break;
+				}
+			case SYM_LTLT:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_LSL;
+					op_typ = OPTYP_LSL;
+					break;
+				}
+			case SYM_GTGT:
+				// Binary (st0 != 0)
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					printf("Invalid %snary '%s' in #if expression\n", "bi", sym2str[tok->tokv.sym]);
+					goto eval_fail;
+				} else {
+					op_lvl = OPLVL_LSR;
+					op_typ = OPTYP_LSR;
+					break;
+				}
+				
+			case SYM_LPAREN:
+				// May be placed anywhere a constant is expected (st0 == 0) and simply pushes a new stack
+				if (vector_last(ppeaux, stack).st0 == 0) {
+					if (!vector_push(ppeaux, stack, (preproc_eval_aux_t){0})) {
+						printf("Failed to push to the stack during #if evaluation\n");
+						goto eval_fail;
+					}
+					goto done_partial_eval;
+				} else {
+					printf("Invalid opening parenthesis in #if expression\n");
+					goto eval_fail;
+				}
+			case SYM_RPAREN:
+			case SYM_QUESTION:
+				// May be placed anywhere and simply pushes a new stack (paren) or more complex operation (ternary)
+				if ((tok->tokv.sym == SYM_RPAREN) && (vector_size(ppeaux, stack) == 1)) {
+					printf("Invalid closing parenthesis during #if evaluation\n");
+					goto eval_fail;
+				}
+				if (!vector_last(ppeaux, stack).st0) {
+					printf("Invalid %s during #if evaluation\n", (tok->tokv.sym == SYM_RPAREN) ? "closing parenthesis" : "question mark");
+					goto eval_fail;
+				}
+				
+				// Evaluate the top of the stack, then pop
+				acc = vector_last(ppeaux, stack).v0;
+				vector_last(ppeaux, stack).st0 = 0;
+				
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wconversion"
+#pragma GCC diagnostic ignored "-Wduplicated-cond" // For the else if (.stX)
+				if (vector_last(ppeaux, stack).st2 == OPTYP_MUL) {
+					acc = vector_last(ppeaux, stack).v2 * acc;
+					vector_last(ppeaux, stack).st2 = 0;
+				} else if (vector_last(ppeaux, stack).st2 == OPTYP_DIV) {
+					acc = vector_last(ppeaux, stack).v2 / acc;
+					vector_last(ppeaux, stack).st2 = 0;
+				} else if (vector_last(ppeaux, stack).st2) {
+					printf("<internal error> Unknown st2 %d during #if evaluation\n", vector_last(ppeaux, stack).st2);
+					goto eval_fail;
+				}
+				if (vector_last(ppeaux, stack).st3 == OPTYP_ADD) {
+					acc = vector_last(ppeaux, stack).v3 + acc;
+					vector_last(ppeaux, stack).st3 = 0;
+				} else if (vector_last(ppeaux, stack).st3 == OPTYP_SUB) {
+					acc = vector_last(ppeaux, stack).v3 - acc;
+					vector_last(ppeaux, stack).st3 = 0;
+				} else if (vector_last(ppeaux, stack).st3) {
+					printf("<internal error> Unknown st3 %d during #if evaluation\n", vector_last(ppeaux, stack).st3);
+					goto eval_fail;
+				}
+				if (vector_last(ppeaux, stack).st4 == OPTYP_LSL) {
+					acc = vector_last(ppeaux, stack).v4 << acc;
+					vector_last(ppeaux, stack).st4 = 0;
+				} else if (vector_last(ppeaux, stack).st4 == OPTYP_LSR) {
+					acc = vector_last(ppeaux, stack).v4 >> acc;
+					vector_last(ppeaux, stack).st4 = 0;
+				} else if (vector_last(ppeaux, stack).st4) {
+					printf("<internal error> Unknown st4 %d during #if evaluation\n", vector_last(ppeaux, stack).st4);
+					goto eval_fail;
+				}
+				if (vector_last(ppeaux, stack).st5 == OPTYP_LET) {
+					acc = vector_last(ppeaux, stack).v5 < acc;
+					vector_last(ppeaux, stack).st5 = 0;
+				} else if (vector_last(ppeaux, stack).st5 == OPTYP_LEE) {
+					acc = vector_last(ppeaux, stack).v5 <= acc;
+					vector_last(ppeaux, stack).st5 = 0;
+				} else if (vector_last(ppeaux, stack).st5 == OPTYP_GRT) {
+					acc = vector_last(ppeaux, stack).v5 > acc;
+					vector_last(ppeaux, stack).st5 = 0;
+				} else if (vector_last(ppeaux, stack).st5 == OPTYP_GRE) {
+					acc = vector_last(ppeaux, stack).v5 >= acc;
+					vector_last(ppeaux, stack).st5 = 0;
+				} else if (vector_last(ppeaux, stack).st5) {
+					printf("<internal error> Unknown st5 %d during #if evaluation\n", vector_last(ppeaux, stack).st5);
+					goto eval_fail;
+				}
+				if (vector_last(ppeaux, stack).st6 == OPTYP_EQU) {
+					acc = vector_last(ppeaux, stack).v6 == acc;
+					vector_last(ppeaux, stack).st6 = 0;
+				} else if (vector_last(ppeaux, stack).st6 == OPTYP_NEQ) {
+					acc = vector_last(ppeaux, stack).v6 != acc;
+					vector_last(ppeaux, stack).st6 = 0;
+				} else if (vector_last(ppeaux, stack).st6) {
+					printf("<internal error> Unknown st6 %d during #if evaluation\n", vector_last(ppeaux, stack).st6);
+					goto eval_fail;
+				}
+				if (vector_last(ppeaux, stack).st7 == OPTYP_AAN) {
+					acc = vector_last(ppeaux, stack).v7 & acc;
+					vector_last(ppeaux, stack).st7 = 0;
+				} else if (vector_last(ppeaux, stack).st7) {
+					printf("<internal error> Unknown st7 %d during #if evaluation\n", vector_last(ppeaux, stack).st7);
+					goto eval_fail;
+				}
+				if (vector_last(ppeaux, stack).st8 == OPTYP_XOR) {
+					acc = vector_last(ppeaux, stack).v8 ^ acc;
+					vector_last(ppeaux, stack).st8 = 0;
+				} else if (vector_last(ppeaux, stack).st8) {
+					printf("<internal error> Unknown st8 %d during #if evaluation\n", vector_last(ppeaux, stack).st8);
+					goto eval_fail;
+				}
+				if (vector_last(ppeaux, stack).st9 == OPTYP_AOR) {
+					acc = vector_last(ppeaux, stack).v9 | acc;
+					vector_last(ppeaux, stack).st9 = 0;
+				} else if (vector_last(ppeaux, stack).st9) {
+					printf("<internal error> Unknown st9 %d during #if evaluation\n", vector_last(ppeaux, stack).st9);
+					goto eval_fail;
+				}
+				if (vector_last(ppeaux, stack).st_bool) {
+					acc = !!acc;
+					vector_last(ppeaux, stack).st_bool = 0;
+				}
+#pragma GCC diagnostic pop
+				
+				if (tok->tokv.sym == SYM_RPAREN) {
+					vector_pop(ppeaux, stack);
+					goto push_acc_to_st0;
+				} else {
+				eval_question_acc:
+					if (acc) {
+						// Increase n_colons
+						++vector_last(ppeaux, stack).n_colons;
+						goto done_partial_eval;
+					} else {
+						// Skip to the corresponding colon
+						unsigned nquestions = 0, nparens = 0;
+						// Note that we don't really care about the content of the ignored part; it may be syntaxically incorrect
+						for (++tok; tok < vector_end(preproc, cond); ++tok) {
+							if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_LPAREN)) {
+								++nparens;
+							} else if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) {
+								if (nparens) --nparens;
+								else {
+									printf("Unclosed parenthesis in #if evaluation\n");
+									goto eval_fail;
+								}
+							} else if (!nparens && (tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_QUESTION)) {
+								++nquestions;
+							} else if (!nparens && (tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_COLON)) {
+								if (nquestions) --nquestions;
+								else break;
+							}
+						}
+						if (tok == vector_end(preproc, cond)) {
+							printf("Unfinished ternary operator in #if evaluation\n");
+							goto eval_fail;
+						}
+						goto done_partial_eval;
+					}
+				}
+			case SYM_COLON:
+				if (vector_last(ppeaux, stack).n_colons) {
+					// Decrease n_colons and skip to the rparen/end of vector
+					if (vector_size(ppeaux, stack) == 1) {
+						goto done_complete_stack; // No rparen, skip to the end of the vector
+					}
+					// Skip to the next rparen; also, we skip the correct count of ':' since we don't care about those anymore
+					// --vector_last(ppeaux, stack).n_colons;
+					unsigned nparens = 0;
+					// Note that we don't really care about the content of the ignored part; it may be syntaxically incorrect
+					for (++tok; tok < vector_end(preproc, cond); ++tok) {
+						if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_LPAREN)) {
+							++nparens;
+						} else if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) {
+							if (nparens) --nparens;
+							else break;
+						}
+					}
+					if (tok == vector_end(preproc, cond)) {
+						printf("Unfinished ternary operator in #if evaluation\n");
+						goto eval_fail;
+					}
+					--tok;
+					goto done_partial_eval;
+				} else {
+					printf("Invalid colon symbol during #if evaluation\n");
+					goto eval_fail;
+				}
+				
+			case SYM_LBRACKET:
+			case SYM_RBRACKET:
+			case SYM_LSQBRACKET:
+			case SYM_RSQBRACKET:
+			case SYM_HASH:
+			case SYM_HASHHASH:
+			case SYM_SEMICOLON:
+			case SYM_COLONCOLON:
+			case SYM_VARIADIC:
+			case SYM_DOT:
+			case SYM_DASHGT:
+			case SYM_PERCENT:
+			case SYM_EQ:
+			case SYM_PLUSEQ:
+			case SYM_DASHEQ:
+			case SYM_STAREQ:
+			case SYM_SLASHEQ:
+			case SYM_PERCENTEQ:
+			case SYM_HATEQ:
+			case SYM_AMPEQ:
+			case SYM_PIPEEQ:
+			case SYM_LTLTEQ:
+			case SYM_GTGTEQ:
+			case SYM_PLUSPLUS:
+			case SYM_DASHDASH:
+			case SYM_COMMA:
+			default:
+				printf("Invalid symbol ID %u during #if evaluation\n", tok->tokv.sym);
+				goto eval_fail;
+			}
+			acc = vector_last(ppeaux, stack).v0;
+			vector_last(ppeaux, stack).st0 = 0;
+			
+			if (op_lvl < 2) {
+				printf("<internal error> Invalid op_lvl %d < 2 during #if evaluation\n", op_lvl);
+				goto eval_fail;
+			}
+			
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wconversion"
+#pragma GCC diagnostic ignored "-Wduplicated-cond" // For the else if (.stX)
+			if (vector_last(ppeaux, stack).st2 == OPTYP_MUL) {
+				acc = vector_last(ppeaux, stack).v2 * acc;
+				vector_last(ppeaux, stack).st2 = 0;
+			} else if (vector_last(ppeaux, stack).st2 == OPTYP_DIV) {
+				acc = vector_last(ppeaux, stack).v2 / acc;
+				vector_last(ppeaux, stack).st2 = 0;
+			} else if (vector_last(ppeaux, stack).st2) {
+				printf("<internal error> Unknown st2 %d during #if evaluation\n", vector_last(ppeaux, stack).st2);
+				goto eval_fail;
+			}
+			if (op_lvl == 2) {
+				vector_last(ppeaux, stack).st2 = op_typ;
+				vector_last(ppeaux, stack).v2 = acc;
+				goto done_partial_eval;
+			}
+			if (vector_last(ppeaux, stack).st3 == OPTYP_ADD) {
+				acc = vector_last(ppeaux, stack).v3 + acc;
+				vector_last(ppeaux, stack).st3 = 0;
+			} else if (vector_last(ppeaux, stack).st3 == OPTYP_SUB) {
+				acc = vector_last(ppeaux, stack).v3 - acc;
+				vector_last(ppeaux, stack).st3 = 0;
+			} else if (vector_last(ppeaux, stack).st3) {
+				printf("<internal error> Unknown st3 %d during #if evaluation\n", vector_last(ppeaux, stack).st3);
+				goto eval_fail;
+			}
+			if (op_lvl == 3) {
+				vector_last(ppeaux, stack).st3 = op_typ;
+				vector_last(ppeaux, stack).v3 = acc;
+				goto done_partial_eval;
+			}
+			if (vector_last(ppeaux, stack).st4 == OPTYP_LSL) {
+				acc = vector_last(ppeaux, stack).v4 << acc;
+				vector_last(ppeaux, stack).st4 = 0;
+			} else if (vector_last(ppeaux, stack).st4 == OPTYP_LSR) {
+				acc = vector_last(ppeaux, stack).v4 >> acc;
+				vector_last(ppeaux, stack).st4 = 0;
+			} else if (vector_last(ppeaux, stack).st4) {
+				printf("<internal error> Unknown st4 %d during #if evaluation\n", vector_last(ppeaux, stack).st4);
+				goto eval_fail;
+			}
+			if (op_lvl == 4) {
+				vector_last(ppeaux, stack).st4 = op_typ;
+				vector_last(ppeaux, stack).v4 = acc;
+				goto done_partial_eval;
+			}
+			if (vector_last(ppeaux, stack).st5 == OPTYP_LET) {
+				acc = vector_last(ppeaux, stack).v5 < acc;
+				vector_last(ppeaux, stack).st5 = 0;
+			} else if (vector_last(ppeaux, stack).st5 == OPTYP_LEE) {
+				acc = vector_last(ppeaux, stack).v5 <= acc;
+				vector_last(ppeaux, stack).st5 = 0;
+			} else if (vector_last(ppeaux, stack).st5 == OPTYP_GRT) {
+				acc = vector_last(ppeaux, stack).v5 > acc;
+				vector_last(ppeaux, stack).st5 = 0;
+			} else if (vector_last(ppeaux, stack).st5 == OPTYP_GRE) {
+				acc = vector_last(ppeaux, stack).v5 >= acc;
+				vector_last(ppeaux, stack).st5 = 0;
+			} else if (vector_last(ppeaux, stack).st5) {
+				printf("<internal error> Unknown st5 %d during #if evaluation\n", vector_last(ppeaux, stack).st5);
+				goto eval_fail;
+			}
+			if (op_lvl == 5) {
+				vector_last(ppeaux, stack).st5 = op_typ;
+				vector_last(ppeaux, stack).v5 = acc;
+				goto done_partial_eval;
+			}
+			if (vector_last(ppeaux, stack).st6 == OPTYP_EQU) {
+				acc = vector_last(ppeaux, stack).v6 == acc;
+				vector_last(ppeaux, stack).st6 = 0;
+			} else if (vector_last(ppeaux, stack).st6 == OPTYP_NEQ) {
+				acc = vector_last(ppeaux, stack).v6 != acc;
+				vector_last(ppeaux, stack).st6 = 0;
+			} else if (vector_last(ppeaux, stack).st6) {
+				printf("<internal error> Unknown st6 %d during #if evaluation\n", vector_last(ppeaux, stack).st6);
+				goto eval_fail;
+			}
+			if (op_lvl == 6) {
+				vector_last(ppeaux, stack).st6 = op_typ;
+				vector_last(ppeaux, stack).v6 = acc;
+				goto done_partial_eval;
+			}
+			if (vector_last(ppeaux, stack).st7 == OPTYP_AAN) {
+				acc = vector_last(ppeaux, stack).v7 & acc;
+				vector_last(ppeaux, stack).st7 = 0;
+			} else if (vector_last(ppeaux, stack).st7) {
+				printf("<internal error> Unknown st7 %d during #if evaluation\n", vector_last(ppeaux, stack).st7);
+				goto eval_fail;
+			}
+			if (op_lvl == 7) {
+				vector_last(ppeaux, stack).st7 = op_typ;
+				vector_last(ppeaux, stack).v7 = acc;
+				goto done_partial_eval;
+			}
+			if (vector_last(ppeaux, stack).st8 == OPTYP_XOR) {
+				acc = vector_last(ppeaux, stack).v8 ^ acc;
+				vector_last(ppeaux, stack).st8 = 0;
+			} else if (vector_last(ppeaux, stack).st8) {
+				printf("<internal error> Unknown st8 %d during #if evaluation\n", vector_last(ppeaux, stack).st8);
+				goto eval_fail;
+			}
+			if (op_lvl == 8) {
+				vector_last(ppeaux, stack).st8 = op_typ;
+				vector_last(ppeaux, stack).v8 = acc;
+				goto done_partial_eval;
+			}
+			if (vector_last(ppeaux, stack).st9 == OPTYP_AOR) {
+				acc = vector_last(ppeaux, stack).v9 | acc;
+				vector_last(ppeaux, stack).st9 = 0;
+			} else if (vector_last(ppeaux, stack).st9) {
+				printf("<internal error> Unknown st9 %d during #if evaluation\n", vector_last(ppeaux, stack).st9);
+				goto eval_fail;
+			}
+			if (op_lvl == 9) {
+				vector_last(ppeaux, stack).st9 = op_typ;
+				vector_last(ppeaux, stack).v9 = acc;
+				goto done_partial_eval;
+			}
+			if (op_lvl == 10) {
+				// We know that sym == SYM_AMPAMP, so we need to skip evaluating the remainder if it equals 0
+				if (acc) goto done_partial_eval;
+				unsigned nparens = 0;
+				// 0 && y ? z : w => w
+				// 0 && y || z => z
+				// Otherwise, keep skipping to the next token
+				for (++tok; tok < vector_end(preproc, cond); ++tok) {
+					if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_LPAREN)) {
+						++nparens;
+					} else if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) {
+						if (nparens) --nparens;
+						else {
+							vector_last(ppeaux, stack).v0 = acc;
+							vector_last(ppeaux, stack).st0 = 1;
+							--tok;
+							goto done_partial_eval;
+						}
+					} else if (!nparens && (tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_QUESTION)) {
+						goto eval_question_acc;
+					} else if (!nparens && (tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_PIPEPIPE)) {
+						break;
+					}
+				}
+				if (tok == vector_end(preproc, cond)) {
+					if (!nparens) goto done_complete_acc;
+					else {
+						printf("Unclosed parenthesis in #if evaluation\n");
+						goto eval_fail;
+					}
+				}
+				goto done_partial_eval;
+			}
+			if (op_lvl == 11) {
+				// We know that sym == SYM_PIPEPIPE, so we need to skip evaluating the remainder if it equals 1
+				if (!acc) goto done_partial_eval;
+				unsigned nparens = 0;
+				// 0 || y ? z : w => w
+				// Otherwise, keep skipping to the next token
+				for (++tok; tok < vector_end(preproc, cond); ++tok) {
+					if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_LPAREN)) {
+						++nparens;
+					} else if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) {
+						if (nparens) --nparens;
+						else {
+							vector_last(ppeaux, stack).v0 = acc;
+							vector_last(ppeaux, stack).st0 = 1;
+							--tok;
+							goto done_partial_eval;
+						}
+					} else if (!nparens && (tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_QUESTION)) {
+						goto eval_question_acc;
+					}
+				}
+				if (tok == vector_end(preproc, cond)) {
+					if (!nparens) goto done_complete_acc;
+					else {
+						printf("Unclosed parenthesis in #if evaluation\n");
+						goto eval_fail;
+					}
+				}
+				goto done_partial_eval;
+			}
+			// if (vector_last(ppeaux, stack).st_bool) {
+			// 	acc = !!acc;
+			// 	vector_last(ppeaux, stack).st_bool = 0;
+			// }
+#pragma GCC diagnostic pop
+			printf("<internal error> Invalid op_lvl %d > 11 during #if evaluation\n", op_lvl);
+			goto eval_fail;
+			
+		done_partial_eval:
+		} else {
+			printf("Invalid token type %u during #if evaluation\n", tok->tokt);
+			goto eval_fail;
+		}
+		
+		// push_acc_to_st0: evaluate all held st1 operations on acc, then set st0 to 1 and v0 to acc
+		if (0) {
+		push_acc_to_st0:
+			while (vector_last(ppeaux, stack).st1) {
+				// Only symbols, so this should be safe
+				enum token_sym_type_e sym = vector_last(ppeaux, stack).v1[--vector_last(ppeaux, stack).st1].tokv.sym;
+				if (sym == SYM_PLUS) {}
+				else if (sym == SYM_DASH) acc = -acc;
+				else if (sym == SYM_EXCL) acc = !acc;
+				else if (sym == SYM_TILDE) acc = ~acc;
+				else {
+					printf("<internal error> Unknown level 1 unary operator sym ID %u\n", sym);
+				}
+			}
+			vector_last(ppeaux, stack).v0 = acc;
+			vector_last(ppeaux, stack).st0 = 1;
+		}
+	}
+	
+	if (vector_size(ppeaux, stack) != 1) {
+		printf("Opened parenthesis never closed in #if expression\n");
+		goto eval_fail;
+	}
+	
+done_complete_stack:
+	acc = vector_last(ppeaux, stack).v0;
+	vector_last(ppeaux, stack).st0 = 0;
+	
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wconversion"
+#pragma GCC diagnostic ignored "-Wduplicated-cond" // For the else if (.stX)
+	if (vector_last(ppeaux, stack).st2 == OPTYP_MUL) {
+		acc = vector_last(ppeaux, stack).v2 * acc;
+		vector_last(ppeaux, stack).st2 = 0;
+	} else if (vector_last(ppeaux, stack).st2 == OPTYP_DIV) {
+		acc = vector_last(ppeaux, stack).v2 / acc;
+		vector_last(ppeaux, stack).st2 = 0;
+	} else if (vector_last(ppeaux, stack).st2) {
+		printf("<internal error> Unknown st2 %d during #if evaluation\n", vector_last(ppeaux, stack).st2);
+		goto eval_fail;
+	}
+	if (vector_last(ppeaux, stack).st3 == OPTYP_ADD) {
+		acc = vector_last(ppeaux, stack).v3 + acc;
+		vector_last(ppeaux, stack).st3 = 0;
+	} else if (vector_last(ppeaux, stack).st3 == OPTYP_SUB) {
+		acc = vector_last(ppeaux, stack).v3 - acc;
+		vector_last(ppeaux, stack).st3 = 0;
+	} else if (vector_last(ppeaux, stack).st3) {
+		printf("<internal error> Unknown st3 %d during #if evaluation\n", vector_last(ppeaux, stack).st3);
+		goto eval_fail;
+	}
+	if (vector_last(ppeaux, stack).st4 == OPTYP_LSL) {
+		acc = vector_last(ppeaux, stack).v4 << acc;
+		vector_last(ppeaux, stack).st4 = 0;
+	} else if (vector_last(ppeaux, stack).st4 == OPTYP_LSR) {
+		acc = vector_last(ppeaux, stack).v4 >> acc;
+		vector_last(ppeaux, stack).st4 = 0;
+	} else if (vector_last(ppeaux, stack).st4) {
+		printf("<internal error> Unknown st4 %d during #if evaluation\n", vector_last(ppeaux, stack).st4);
+		goto eval_fail;
+	}
+	if (vector_last(ppeaux, stack).st5 == OPTYP_LET) {
+		acc = vector_last(ppeaux, stack).v5 < acc;
+		vector_last(ppeaux, stack).st5 = 0;
+	} else if (vector_last(ppeaux, stack).st5 == OPTYP_LEE) {
+		acc = vector_last(ppeaux, stack).v5 <= acc;
+		vector_last(ppeaux, stack).st5 = 0;
+	} else if (vector_last(ppeaux, stack).st5 == OPTYP_GRT) {
+		acc = vector_last(ppeaux, stack).v5 > acc;
+		vector_last(ppeaux, stack).st5 = 0;
+	} else if (vector_last(ppeaux, stack).st5 == OPTYP_GRE) {
+		acc = vector_last(ppeaux, stack).v5 >= acc;
+		vector_last(ppeaux, stack).st5 = 0;
+	} else if (vector_last(ppeaux, stack).st5) {
+		printf("<internal error> Unknown st5 %d during #if evaluation\n", vector_last(ppeaux, stack).st5);
+		goto eval_fail;
+	}
+	if (vector_last(ppeaux, stack).st6 == OPTYP_EQU) {
+		acc = vector_last(ppeaux, stack).v6 == acc;
+		vector_last(ppeaux, stack).st6 = 0;
+	} else if (vector_last(ppeaux, stack).st6 == OPTYP_NEQ) {
+		acc = vector_last(ppeaux, stack).v6 != acc;
+		vector_last(ppeaux, stack).st6 = 0;
+	} else if (vector_last(ppeaux, stack).st6) {
+		printf("<internal error> Unknown st6 %d during #if evaluation\n", vector_last(ppeaux, stack).st6);
+		goto eval_fail;
+	}
+	if (vector_last(ppeaux, stack).st7 == OPTYP_AAN) {
+		acc = vector_last(ppeaux, stack).v7 & acc;
+		vector_last(ppeaux, stack).st7 = 0;
+	} else if (vector_last(ppeaux, stack).st7) {
+		printf("<internal error> Unknown st7 %d during #if evaluation\n", vector_last(ppeaux, stack).st7);
+		goto eval_fail;
+	}
+	if (vector_last(ppeaux, stack).st8 == OPTYP_XOR) {
+		acc = vector_last(ppeaux, stack).v8 ^ acc;
+		vector_last(ppeaux, stack).st8 = 0;
+	} else if (vector_last(ppeaux, stack).st8) {
+		printf("<internal error> Unknown st8 %d during #if evaluation\n", vector_last(ppeaux, stack).st8);
+		goto eval_fail;
+	}
+	if (vector_last(ppeaux, stack).st9 == OPTYP_AOR) {
+		acc = vector_last(ppeaux, stack).v9 | acc;
+		vector_last(ppeaux, stack).st9 = 0;
+	} else if (vector_last(ppeaux, stack).st9) {
+		printf("<internal error> Unknown st9 %d during #if evaluation\n", vector_last(ppeaux, stack).st9);
+		goto eval_fail;
+	}
+	if (vector_last(ppeaux, stack).st_bool) {
+		acc = !!acc;
+		vector_last(ppeaux, stack).st_bool = 0;
+	}
+#pragma GCC diagnostic pop
+	
+done_complete_acc:
+	vector_del(ppeaux, stack);
+	*aux_ret = 1;
+	return acc;
+}
+
+int proc_unget_token(preproc_t *src, proc_token_t *tok) {
+	return vector_push(ppsource, src->prep, ((ppsource_t){ .srct = PPSRC_PTOKEN, .srcv.ptok = *tok }));
+}
+static proc_token_t proc_next_token_aux(preproc_t *src) {
+	if (!vector_size(ppsource, src->prep)) {
+		return (proc_token_t){ .tokt = PTOK_EOF, .tokv = (union proc_token_val_u){.c = (char)EOF} };
+	}
+	if (vector_last(ppsource, src->prep).srct == PPSRC_PTOKEN) {
+		proc_token_t ret = vector_last(ppsource, src->prep).srcv.ptok;
+		vector_pop(ppsource, src->prep);
+		return ret;
+	}
+check_if_depth:
+	{
+		if (!vector_size(ppsource, src->prep)) {
+			return (proc_token_t){ .tokt = PTOK_EOF, .tokv = (union proc_token_val_u){.c = (char)EOF} };
+		}
+		ppsource_t *ppsrc = &vector_last(ppsource, src->prep);
+		if ((ppsrc->srct == PPSRC_PREPARE) && (ppsrc->srcv.prep.ok_depth != ppsrc->srcv.prep.cond_depth)) {
+			// Ignore all tokens from ppsrc until:
+			//  INVALID -------------- abort (return PTOK_INVALID)
+			//  EOF ------------------ print an error, then pop and goto check_if_depth
+			//  # if/elif/endif/... -- parse, execute, goto check_if_depth
+			// Also, track newlines to keep src->st up-to-date
+			
+			while (1) {
+				preproc_token_t tok = ppsrc_next_token(src);
+			skip_cur_token:
+				if (tok.tokt == PPTOK_INVALID) {
+					src->st = PPST_NONE;
+					proc_token_t ret;
+					ret.tokt = PTOK_INVALID;
+					ret.tokv = (union proc_token_val_u){.c = tok.tokv.c};
+					return ret;
+				} else if (tok.tokt == PPTOK_NEWLINE) {
+					src->st = PPST_NL;
+				} else if (tok.tokt == PPTOK_EOF) {
+					printf("Error: file ended before closing all conditionals (ignoring)\n");
+					vector_pop(ppsource, src->prep);
+					goto check_if_depth;
+				} else if ((tok.tokt == PPTOK_SYM) && (src->st == PPST_NL) && (tok.tokv.sym == SYM_HASH)) {
+					src->st = PPST_NONE;
+					tok = ppsrc_next_token(src);
+					if ((tok.tokt == PPTOK_NEWLINE) || (tok.tokt == PPTOK_EOF)) {
+						// Empty preprocessor command
+						src->st = PPST_NL;
+						continue;
+					}
+					if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) goto preproc_ignore_remaining;
+					if (!strcmp(string_content(tok.tokv.str), "include")) goto preproc_ignore_remaining;
+					else if (!strcmp(string_content(tok.tokv.str), "include_next")) goto preproc_ignore_remaining;
+					else if (!strcmp(string_content(tok.tokv.str), "define")) goto preproc_ignore_remaining;
+					else if (!strcmp(string_content(tok.tokv.str), "undef")) goto preproc_ignore_remaining;
+					else if (!strcmp(string_content(tok.tokv.str), "error")) goto preproc_ignore_remaining;
+					else if (!strcmp(string_content(tok.tokv.str), "warning")) goto preproc_ignore_remaining; // C23/extension
+					else if (!strcmp(string_content(tok.tokv.str), "pragma")) goto preproc_ignore_remaining;
+					else if (!strcmp(string_content(tok.tokv.str), "line")) goto preproc_ignore_remaining;
+					else if (!strcmp(string_content(tok.tokv.str), "if")) {
+						// Increase cond_depth (we already know we are ignoring the content)
+						++ppsrc->srcv.prep.cond_depth;
+						goto preproc_ignore_remaining;
+					} else if (!strcmp(string_content(tok.tokv.str), "ifdef")) {
+						// Increase cond_depth (we already know we are ignoring the content)
+						++ppsrc->srcv.prep.cond_depth;
+						goto preproc_ignore_remaining;
+					} else if (!strcmp(string_content(tok.tokv.str), "ifndef")) {
+						// Increase cond_depth (we already know we are ignoring the content)
+						++ppsrc->srcv.prep.cond_depth;
+						goto preproc_ignore_remaining;
+					} else if (!strcmp(string_content(tok.tokv.str), "elif")) {
+						if ((ppsrc->srcv.prep.ok_depth == ppsrc->srcv.prep.cond_depth - 1) && !ppsrc->srcv.prep.entered_next_ok_cond) {
+							string_del(tok.tokv.str);
+							VECTOR(preproc) *cond = vector_new(preproc);
+							if (!cond) {
+								printf("Error: failed to allocate #elif condition vector\n");
+								src->st = PPST_NONE;
+								return (proc_token_t){ .tokt = PTOK_INVALID, .tokv = (union proc_token_val_u){.c = '\0'} };
+							}
+							tok = ppsrc_next_token(src);
+							while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) {
+								if (!vector_push(preproc, cond, tok)) {
+									printf("Error: failed to add token to #elif condition vector\n");
+									vector_del(preproc, cond);
+									src->st = PPST_NONE;
+									return (proc_token_t){ .tokt = PTOK_INVALID, .tokv = (union proc_token_val_u){.c = '\0'} };
+								}
+								tok = ppsrc_next_token(src);
+							}
+							vector_trim(preproc, cond);
+							khash_t(string_set) *solved_macros = kh_init(string_set);
+							if (!solved_macros) {
+								printf("Error: failed to allocate #elif solved_macros set\n");
+								vector_del(preproc, cond);
+								src->st = PPST_NONE;
+								return (proc_token_t){ .tokt = PTOK_INVALID, .tokv = (union proc_token_val_u){.c = '\0'} };
+							}
+							
+							VECTOR(preproc) *expanded = proc_do_expand(src->macros_map, cond, solved_macros, src->macros_used);
+							vector_del(preproc, cond);
+							macros_set_del(solved_macros);
+							if (!expanded) {
+								printf("Error: failed to expand #elif condition\n");
+								src->st = PPST_NONE;
+								return (proc_token_t){ .tokt = PTOK_INVALID, .tokv = (union proc_token_val_u){.c = '\0'} };
+							}
+							
+							// Now we need to compute what is pointed by expanded, and increase cond_depth and ok_depth as needed
+							int st;
+							int64_t res = preproc_eval(expanded, &st);
+							vector_del(preproc, expanded);
+							if (!st) {
+								printf("Error: failed to evaluate #elif condition in (%s)\n", src->cur_file);
+								src->st = PPST_NONE;
+								return (proc_token_t){ .tokt = PTOK_INVALID, .tokv = (union proc_token_val_u){.c = '\0'} };
+							}
+							if (res) {
+								vector_last(ppsource, src->prep).srcv.prep.entered_next_ok_cond = 1;
+								++vector_last(ppsource, src->prep).srcv.prep.ok_depth;
+								if (tok.tokt == PPTOK_NEWLINE) {
+									src->st = PPST_NL;
+									goto check_next_token;
+								}
+							} else {
+								if (tok.tokt == PPTOK_NEWLINE) {
+									src->st = PPST_NL;
+									continue;
+								}
+							}
+							if (tok.tokt == PPTOK_EOF) {
+								printf("Error: file ended before closing all conditionals (ignoring)\n");
+								vector_pop(ppsource, src->prep);
+								src->st = PPST_NL; // Should be redundant since TOK_NEWLINE is added before TOK_EOF if required
+								// EOF has an empty destructor
+								// Note that since we have opened the file, the previous file had ok_depth == cond_depth
+								goto check_next_token;
+							} else /* if (tok.tokt == PPTOK_INVALID) */ {
+								src->st = PPST_NONE;
+								return (proc_token_t){ .tokt = PTOK_INVALID, .tokv.c = tok.tokv.c };
+							}
+						} else goto preproc_ignore_remaining;
+					} else if (!strcmp(string_content(tok.tokv.str), "elifdef")) {
+						string_del(tok.tokv.str);
+						tok = ppsrc_next_token(src);
+						if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) {
+							printf("Invalid token type %u after '#elifdef' preprocessor command\n", tok.tokt);
+							goto preproc_ignore_remaining;
+						}
+						if ((ppsrc->srcv.prep.ok_depth == ppsrc->srcv.prep.cond_depth - 1) && !ppsrc->srcv.prep.entered_next_ok_cond) {
+							// We may enter the following block, check it
+							khiter_t it = kh_get(macros_map, src->macros_map, string_content(tok.tokv.str));
+							if (it != kh_end(src->macros_map)) {
+								ppsrc->srcv.prep.entered_next_ok_cond = 1;
+								++ppsrc->srcv.prep.ok_depth;
+							}
+							goto preproc_ignore_remaining;
+						}
+					} else if (!strcmp(string_content(tok.tokv.str), "elifndef")) {
+						string_del(tok.tokv.str);
+						tok = ppsrc_next_token(src);
+						if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) {
+							printf("Invalid token type %u after '#elifdef' preprocessor command\n", tok.tokt);
+							goto preproc_ignore_remaining;
+						}
+						if ((ppsrc->srcv.prep.ok_depth == ppsrc->srcv.prep.cond_depth - 1) && !ppsrc->srcv.prep.entered_next_ok_cond) {
+							// We may enter the following block, check it
+							khiter_t it = kh_get(macros_map, src->macros_map, string_content(tok.tokv.str));
+							if (it == kh_end(src->macros_map)) {
+								ppsrc->srcv.prep.entered_next_ok_cond = 1;
+								++ppsrc->srcv.prep.ok_depth;
+							}
+							goto preproc_ignore_remaining;
+						}
+					} else if (!strcmp(string_content(tok.tokv.str), "else")) {
+						// Maybe increase ok_depth if ok_depth = cond_depth - 1; also goto check_if_depth
+						// Note that this (very naive) approach allows code such as:
+						/* #ifdef M
+						 * ... // Not preprocessed                       *** Preprocessed
+						 * #else
+						 * ... // Preprocessed                           *** Not preprocessed
+						 * #else
+						 * ... // Not preprocessed due to unrelated code *** Not preprocessed
+						 * #else
+						 * ... // Preprocessed                           *** Not preprocessed
+						 * #endif
+						 */
+						// Forbidding this code would require another 64-bits bitfield, which is redundant, thus not implemented.
+						// Also, we only need to goto check_if_depth if we actually update ok_depth
+						if ((ppsrc->srcv.prep.ok_depth == ppsrc->srcv.prep.cond_depth - 1) && !ppsrc->srcv.prep.entered_next_ok_cond) {
+							++ppsrc->srcv.prep.ok_depth;
+							goto preproc_ignore_remaining_goto;
+						} else goto preproc_ignore_remaining;
+					} else if (!strcmp(string_content(tok.tokv.str), "endif")) {
+						// Decrease cond_depth; also goto check_if_depth
+						// Note that 0 <= ppsrc->srcv.prep.ok_depth < ppsrc->srcv.prep.cond_depth, so this code is OK
+						--ppsrc->srcv.prep.cond_depth;
+						goto preproc_ignore_remaining_goto;
+					}
+					
+					printf("Unknown ignored pp command %s (%s), skipping until EOL\n", string_content(tok.tokv.str), src->cur_file);
+				preproc_ignore_remaining:
+					while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) {
+						preproc_token_del(&tok);
+						tok = ppsrc_next_token(src);
+					}
+					if (tok.tokt == PPTOK_NEWLINE) {
+						src->st = PPST_NL;
+					}
+					goto skip_cur_token;
+					
+				preproc_ignore_remaining_goto:
+					while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) {
+						preproc_token_del(&tok);
+						tok = ppsrc_next_token(src);
+					}
+					if (tok.tokt == PPTOK_NEWLINE) {
+						src->st = PPST_NL;
+					}
+					goto check_if_depth;
+				} else {
+					src->st = PPST_NONE;
+					preproc_token_del(&tok);
+				}
+			}
+		}
+	}
+	proc_token_t ret;
+check_next_token:
+	if (!vector_size(ppsource, src->prep)) {
+		ret.tokt = PTOK_EOF;
+		ret.tokv = (union proc_token_val_u){.c = (char)EOF};
+		return ret;
+	}
+start_next_token:
+	preproc_token_t tok = ppsrc_next_token(src);
+start_cur_token:
+	switch (tok.tokt) {
+	case PPTOK_INVALID:
+		src->st = PPST_NONE;
+		ret.tokt = PTOK_INVALID;
+		ret.tokv = (union proc_token_val_u){.c = tok.tokv.c};
+		return ret;
+	case PPTOK_IDENT:
+		src->st = PPST_NONE;
+		{
+			khiter_t it = kh_get(macros_map, src->macros_map, string_content(tok.tokv.str));
+			if (it != kh_end(src->macros_map)) {
+				macro_t *m = &kh_val(src->macros_map, it);
+				int need_solve = !m->is_funlike;
+				VECTOR(preprocs) *margs = NULL;
+				if (m->is_funlike) {
+					preproc_token_t tok2 = ppsrc_next_token(src);
+					size_t nnls = 0;
+					while (tok2.tokt == PPTOK_NEWLINE) {
+						++nnls;
+						tok2 = ppsrc_next_token(src);
+					}
+					if ((tok2.tokt == PPTOK_SYM) && (tok2.tokv.sym == SYM_LPAREN)) {
+						need_solve = 1;
+						margs = vector_new(preprocs);
+						if (!margs) goto solve_err_mem;
+						VECTOR(preproc) *marg = vector_new(preproc);
+						if (!marg) goto solve_err_mem;
+						while (need_solve && (tok2.tokt != PPTOK_EOF) && (tok2.tokt != PPTOK_INVALID)) {
+							tok2 = ppsrc_next_token(src);
+							if ((need_solve == 1) && (tok2.tokt == PPTOK_SYM) && (tok2.tokv.sym == SYM_COMMA)) {
+								// Possible optimization: emplace NULL if vector_size(marg) == 0
+								// This would avoid allocating a new vector
+								vector_trim(preproc, marg);
+								if (!vector_push(preprocs, margs, marg)) {
+									vector_del(preproc, marg);
+									goto solve_err_mem;
+								}
+								marg = vector_new(preproc);
+								if (!marg) goto solve_err_mem;
+							} else {
+								if (!vector_push(preproc, marg, tok2)) {
+									vector_del(preproc, marg);
+									goto solve_err_mem;
+								}
+							}
+							if ((tok2.tokt == PPTOK_SYM) && (tok2.tokv.sym == SYM_LPAREN)) ++need_solve;
+							else if ((tok2.tokt == PPTOK_SYM) && (tok2.tokv.sym == SYM_RPAREN)) --need_solve;
+						}
+						if (need_solve) {
+							printf("Unfinished fun-like macro %s\n", string_content(tok.tokv.str));
+							vector_del(preprocs, margs);
+							vector_del(preproc, marg);
+							string_del(tok.tokv.str);
+							src->st = PPST_NONE;
+							ret.tokt = PTOK_INVALID;
+							ret.tokv = (union proc_token_val_u){.c = tok.tokv.c};
+							return ret;
+						}
+						// margs finishes with a SYM_RPAREN token
+						vector_pop(preproc, marg);
+						vector_trim(preproc, marg);
+						if (!vector_push(preprocs, margs, marg)) {
+							vector_del(preproc, marg);
+							goto solve_err_mem;
+						}
+						need_solve = 1;
+					} else {
+						if (!vector_reserve(ppsource, src->prep, vector_size(ppsource, src->prep) + nnls + 1)) {
+							printf("Memory error (undoing lookahead for macro use %s)\n", string_content(tok.tokv.str));
+							string_del(tok.tokv.str);
+							preproc_token_del(&tok2);
+							src->st = PPST_NONE;
+							ret.tokt = PTOK_INVALID;
+							ret.tokv = (union proc_token_val_u){.c = '\0'};
+							return ret;
+						}
+						vector_push(ppsource, src->prep, ((ppsource_t){.srct = PPSRC_PPTOKEN, .srcv.pptok = tok2}));
+						while (nnls--) {
+							vector_push(
+								ppsource,
+								src->prep,
+								((ppsource_t){.srct = PPSRC_PPTOKEN, .srcv.pptok = {.tokt = PPTOK_NEWLINE, .tokv.c = '\n'}}));
+						}
+					}
+				}
+				if (need_solve) {
+					khash_t(string_set) *solved_macros = kh_init(string_set);
+					if (!solved_macros) goto solve_err_mem;
+					
+					char *mname = string_steal(tok.tokv.str);
+					
+					VECTOR(preproc) *solved = proc_solve_macro(src->macros_map, mname, m, margs, solved_macros, NULL);
+					if (margs) vector_del(preprocs, margs);
+					macros_set_del(solved_macros);
+					if (!solved) {
+						src->st = PPST_NONE;
+						ret.tokt = PTOK_INVALID;
+						ret.tokv = (union proc_token_val_u){.c = '\0'};
+						return ret;
+					}
+					// If the expansion is empty, don't push it
+					if (vector_size(preproc, solved)) {
+						if (!vector_push(ppsource, src->prep, ((ppsource_t){.srct = PPSRC_PPTOKENS, .srcv.pptoks = {.idx = 0, .toks = solved}}))) {
+							printf("Memory error (pushing expanded macro)\n");
+							vector_del(preproc, solved);
+							src->st = PPST_NONE;
+							ret.tokt = PTOK_INVALID;
+							ret.tokv = (union proc_token_val_u){.c = '\0'};
+							return ret;
+						}
+					} else {
+						vector_del(preproc, solved);
+					}
+					// src->prep is not empty (either through the push or by not popping the top)
+					goto start_next_token;
+				} else {
+					if (margs) vector_del(preprocs, margs);
+				}
+				
+				if (0) {
+				solve_err_mem:
+					printf("Memory error (parsing macro use %s)\n", string_content(tok.tokv.str));
+					if (margs) vector_del(preprocs, margs);
+					string_del(tok.tokv.str);
+					src->st = PPST_NONE;
+					ret.tokt = PTOK_INVALID;
+					ret.tokv = (union proc_token_val_u){.c = '\0'};
+					return ret;
+				}
+			}
+		}
+		/* FALLTHROUGH */
+	case PPTOK_IDENT_UNEXP: {
+		khiter_t it = kh_get(str2kw, str2kw, string_content(tok.tokv.str));
+		if (it == kh_end(str2kw)) {
+			ret.tokt = PTOK_IDENT;
+			ret.tokv = (union proc_token_val_u){.str = tok.tokv.str};
+		} else {
+			string_del(tok.tokv.str);
+			ret.tokt = PTOK_KEYWORD;
+			ret.tokv = (union proc_token_val_u){.kw = kh_val(str2kw, it)};
+		}
+		return ret; }
+	case PPTOK_NUM:
+		src->st = PPST_NONE;
+		ret.tokt = PTOK_NUM;
+		ret.tokv = (union proc_token_val_u){.str = tok.tokv.str};
+		return ret;
+	case PPTOK_STRING:
+		src->st = PPST_NONE;
+		ret.tokt = PTOK_STRING;
+		ret.tokv = (union proc_token_val_u){.sstr = tok.tokv.sstr, .sisstr = tok.tokv.sisstr};
+		return ret;
+	case PPTOK_INCL:
+		src->st = PPST_NONE;
+		ret.tokt = PTOK_INVALID;
+		ret.tokv = (union proc_token_val_u){.c = tok.tokv.sisstr ? '<' : '"'};
+		string_del(tok.tokv.sstr);
+		return ret;
+	case PPTOK_SYM:
+		if ((src->st == PPST_NL) && (tok.tokv.sym == SYM_HASH)) {
+			tok = ppsrc_next_token(src);
+			if ((tok.tokt == PPTOK_NEWLINE) || (tok.tokt == PPTOK_EOF)) {
+				// Empty preprocessor command
+				if (tok.tokt == PPTOK_NEWLINE) {
+					ret.tokt = PTOK_EOF;
+					ret.tokv = (union proc_token_val_u){.c = tok.tokv.c};
+					return ret;
+				} else {
+					goto check_next_token;
+				}
+			}
+			if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) goto preproc_hash_err;
+			if (!strcmp(string_content(tok.tokv.str), "include")) {
+				string_del(tok.tokv.str);
+				tok = ppsrc_next_token(src);
+				if (tok.tokt != PPTOK_INCL) {
+					printf("Invalid token type %u after '#include' preprocessor command\n", tok.tokt);
+					goto preproc_hash_err;
+				}
+				string_t *incl_file = tok.tokv.sstr;
+				int is_sys = src->is_sys || !tok.tokv.sisstr;
+				tok = ppsrc_next_token(src); // Token was moved
+				while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) {
+					// TODO: Print warning 'ignored token(s)'
+					preproc_token_del(&tok);
+					tok = ppsrc_next_token(src);
+				}
+				if ((is_sys || !try_open_dir(src, incl_file)) && !try_open_sys(src, incl_file, 0)) {
+					printf("Failed to open %s\n", string_content(incl_file));
+					string_del(incl_file);
+					goto preproc_hash_err;
+				}
+				string_del(incl_file);
+				if (tok.tokt == PPTOK_NEWLINE) goto check_next_token;
+				else goto start_cur_token;
+			} else if (!strcmp(string_content(tok.tokv.str), "include_next")) {
+				string_del(tok.tokv.str);
+				tok = ppsrc_next_token(src);
+				if (tok.tokt != PPTOK_INCL) {
+					printf("Invalid token type %u after '#include_next' preprocessor command\n", tok.tokt);
+					goto preproc_hash_err;
+				}
+				string_t *incl_file = tok.tokv.sstr;
+				// Assume we only have one #include "..." path
+				tok = ppsrc_next_token(src); // Token was moved
+				while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) {
+					// TODO: Print warning 'ignored token(s)'
+					preproc_token_del(&tok);
+					tok = ppsrc_next_token(src);
+				}
+				// cur_pathno == 0 if cur_file was from an #include "...", otherwise idx + 1
+				if (!try_open_sys(src, incl_file, src->cur_pathno)) {
+					printf("Failed to open %s\n", string_content(incl_file));
+					string_del(incl_file);
+					goto preproc_hash_err;
+				}
+				string_del(incl_file);
+				if (tok.tokt == PPTOK_NEWLINE) goto check_next_token;
+				else goto start_cur_token;
+			} else if (!strcmp(string_content(tok.tokv.str), "define")) {
+				string_del(tok.tokv.str);
+				tok = ppsrc_next_token(src);
+				if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) {
+					printf("Invalid token type %u after '#define' preprocessor command\n", tok.tokt);
+					goto preproc_hash_err;
+				}
+				string_t *defname = tok.tokv.str;
+				macro_t m = (macro_t){ .is_funlike = 0, .has_varargs = 0, .nargs = 0, .toks = vector_new(mtoken) };
+				if (!m.toks) {
+					printf("Failed to allocate token vector for macro %s, returning EOF\n", string_content(defname));
+					string_del(defname); // Token is now freed
+					ret.tokt = PTOK_EOF;
+					ret.tokv = (union proc_token_val_u){.c = (char)EOF};
+					return ret;
+				}
+				khash_t(argid_map) *args = kh_init(argid_map);
+				if (!args) {
+					printf("Failed to allocate args map for macro %s, returning EOF\n", string_content(defname));
+					string_del(defname); // Token is now freed
+					vector_del(mtoken, m.toks);
+					ret.tokt = PTOK_EOF;
+					ret.tokv = (union proc_token_val_u){.c = (char)EOF};
+					return ret;
+				}
+				tok = ppsrc_next_token(src);
+				if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_LPAREN)) {
+					m.is_funlike = 1;
+					m.nargs = 0;
+					tok = ppsrc_next_token(src);
+					int ok;
+					if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_RPAREN)) {
+						ok = 1;
+					} else {
+						ok = 0;
+						while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) {
+							if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_VARIADIC)) {
+								m.has_varargs = 1;
+								int kh_ret;
+								char *tok_str = strdup("__VA_ARGS__");
+								khiter_t kh_k = kh_put(argid_map, args, tok_str, &kh_ret); // Moves the string content
+								if (kh_ret < 0) { // Failed to move, needs to free here
+									printf("Failed to push arg %s for macro %s, returning EOF\n", tok_str, string_content(defname));
+									string_del(defname);
+									free(tok_str);
+									vector_del(mtoken, m.toks);
+									argid_map_del(args);
+									ret.tokt = PTOK_EOF;
+									ret.tokv = (union proc_token_val_u){.c = (char)EOF};
+									return ret;
+								}
+								if (kh_ret == 0) {
+									printf("Duplicate arg %s defining macro %s\n", tok_str, string_content(defname));
+									string_del(defname);
+									vector_del(mtoken, m.toks);
+									argid_map_del(args);
+									goto preproc_hash_err;
+								}
+								kh_val(args, kh_k) = m.nargs;
+								// Empty token destructor
+								tok = ppsrc_next_token(src);
+								if ((tok.tokt != PPTOK_SYM) || (tok.tokv.sym != SYM_RPAREN)) {
+									printf("Invalid token type %u after variadic macro arguments definition\n", tok.tokt);
+									string_del(defname);
+									vector_del(mtoken, m.toks);
+									argid_map_del(args);
+									goto preproc_hash_err;
+								}
+								ok = 1;
+								break;
+							} else if ((tok.tokt == PPTOK_IDENT) || (tok.tokt == PPTOK_IDENT_UNEXP)) {
+								int kh_ret;
+								char *tok_str = string_steal(tok.tokv.str);
+								khiter_t kh_k = kh_put(argid_map, args, tok_str, &kh_ret); // Moves the string content
+								if (kh_ret < 0) { // Failed to move, needs to free here
+									printf("Failed to push arg %s for macro %s, returning EOF\n", tok_str, string_content(defname));
+									string_del(defname);
+									free(tok_str);
+									vector_del(mtoken, m.toks);
+									argid_map_del(args);
+									ret.tokt = PTOK_EOF;
+									ret.tokv = (union proc_token_val_u){.c = (char)EOF};
+									return ret;
+								}
+								if (kh_ret == 0) {
+									printf("Duplicate arg %s defining macro %s\n", tok_str, string_content(defname));
+									string_del(defname);
+									vector_del(mtoken, m.toks);
+									argid_map_del(args);
+									// Token was already deleted, create a fake token
+									tok.tokt = PPTOK_SYM;
+									tok.tokv.sym = LAST_SYM;
+									goto preproc_hash_err;
+								}
+								kh_val(args, kh_k) = m.nargs++;
+								// Token content is the string, which is moved to the vector
+								tok = ppsrc_next_token(src);
+								if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_COMMA)) {
+									// Empty destructor
+									tok = ppsrc_next_token(src);
+								} else if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_RPAREN)) {
+									// Empty destructor
+									ok = 1;
+									break;
+								} else if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_VARIADIC)) {
+									m.has_varargs = 1;
+									--m.nargs;
+									// Empty token destructor
+									tok = ppsrc_next_token(src);
+									if ((tok.tokt != PPTOK_SYM) || (tok.tokv.sym != SYM_RPAREN)) {
+										printf("Invalid token type %u after variadic macro arguments definition\n", tok.tokt);
+										string_del(defname);
+										vector_del(mtoken, m.toks);
+										argid_map_del(args);
+										goto preproc_hash_err;
+									}
+									ok = 1;
+									break;
+								} else {
+									printf("Invalid %s type %u during variadic macro arguments definition\n",
+										(tok.tokt == PPTOK_SYM) ? "symbol" : "token",
+										(tok.tokt == PPTOK_SYM) ? tok.tokv.sym : tok.tokt);
+									string_del(defname);
+									vector_del(mtoken, m.toks);
+									argid_map_del(args);
+									goto preproc_hash_err;
+								}
+							} else {
+								printf("Invalid %s type %u as macro arguments definition name\n",
+									(tok.tokt == PPTOK_SYM) ? "symbol" : "token", (tok.tokt == PPTOK_SYM) ? tok.tokv.sym : tok.tokt);
+								string_del(defname);
+								vector_del(mtoken, m.toks);
+								argid_map_del(args);
+								goto preproc_hash_err;
+							}
+						}
+					}
+					if (!ok) {
+						printf("Invalid macro definition for %s\n", string_content(defname));
+						string_del(defname);
+						vector_del(mtoken, m.toks);
+						argid_map_del(args);
+						goto preproc_hash_err;
+					}
+					// tok is ')', empty destructor
+					tok = ppsrc_next_token(src);
+				}
+				if (tok.tokt == PPTOK_BLANK) {
+					// BLANK has no destructor
+					tok = ppsrc_next_token(src);
+				}
+				// All tokens until INVALID/NL/EOF are macro content
+				int state = 0;
+#define ST_CONCAT 1
+#define ST_STR 2
+				while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) {
+					if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_HASH)) {
+						if (state & ST_STR) {
+							printf("Warning: duplicated stringify in macro definition (defining %s)\n", string_content(defname));
+						} else {
+							state |= ST_STR;
+						}
+						// Empty destructor
+						tok = ppsrc_next_token(src);
+						continue;
+					}
+					if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_HASHHASH)) {
+						if (state & ST_CONCAT) {
+							printf("Warning: duplicated concatenation in macro definition (defining %s)\n", string_content(defname));
+						} else if (!vector_size(mtoken, m.toks)) {
+							printf("Warning: invalid concatenation at start of macro definition (defining %s)\n", string_content(defname));
+						} else {
+							state |= ST_CONCAT;
+						}
+						// Empty destructor
+						tok = ppsrc_next_token(src);
+						continue;
+					}
+					mtoken_t *mtok;
+					unsigned argid = -1u;
+					if (m.is_funlike && ((tok.tokt == PPTOK_IDENT) || (tok.tokt == PPTOK_IDENT_UNEXP))) {
+						khiter_t kh_k = kh_get(argid_map, args, string_content(tok.tokv.str));
+						if (kh_k != kh_end(args)) {
+							string_del(tok.tokv.str); // Token freed
+							argid = kh_val(args, kh_k);
+						}
+					}
+					if (argid != -1u) {
+						mtok = mtoken_new_arg(argid, state & ST_STR); // TODO: check for != NULL
+						state &= ~ST_STR;
+					} else {
+						mtok = mtoken_new_token(tok); // Token moved // TODO: check for != NULL
+						if (state & ST_STR) {
+							printf("Warning: invalid stringify before token (defining %s)\n", string_content(defname));
+							state &= ~ST_STR;
+						}
+					}
+					if (state & ST_CONCAT) {
+						mtoken_t *mtok2 = vector_last(mtoken, m.toks); // Guaranteed to exist before setting ST_CONCAT
+						mtok = mtoken_new_concat(mtok2, mtok); // TODO: check for != NULL
+						vector_last(mtoken, m.toks) = mtok;
+						state &= ~ST_CONCAT;
+					} else {
+						// TODO: check for error
+						vector_push(mtoken, m.toks, mtok);
+					}
+					// mtok moved to the vector
+					tok = ppsrc_next_token(src);
+				}
+#undef ST_CONCAT
+#undef ST_STR
+				argid_map_del(args);
+				if (tok.tokt == PPTOK_INVALID) {
+					// Abort
+					string_del(defname);
+					macro_del(&m);
+					if (tok.tokt == PPTOK_NEWLINE) goto check_next_token;
+					else goto start_cur_token;
+				} else {
+					// NL and EOF have empty destructors
+					khiter_t kh_k;
+					int iret;
+					char *mname_dup = string_steal(defname);
+					// if (m.is_funlike) printf("OK for %s: %u args", mname_dup, m.nargs);
+					// else printf("OK for %s: no arg", mname_dup);
+					// printf("\n");
+					// vector_for(mtoken, it, m.toks) {
+					// 	printf("Macro token: ");
+					// 	print_macro_tok(*it);
+					// 	printf("\n");
+					// }
+					kh_k = kh_put(string_set, src->macros_defined, mname_dup, &iret);
+					// TODO: check iret?
+					if (iret >= 1) {
+						mname_dup = strdup(mname_dup);
+					}
+					kh_k = kh_put(macros_map, src->macros_map, mname_dup, &iret);
+					if (iret < 0) {
+						// Abort
+						printf("Failed to remember macro %s, aborting\n", mname_dup);
+						free(mname_dup);
+						macro_del(&m);
+						src->st = PPST_NONE;
+						ret.tokt = PTOK_INVALID;
+						ret.tokv = (union proc_token_val_u){.c = tok.tokv.c};
+						return ret;
+					} else if (iret == 0) {
+						// Ignore
+						// Too noisy  printf("Duplicated macro %s\n", mname_dup);
+						free(mname_dup);
+						macro_del(&m);
+					} else {
+						kh_val(src->macros_map, kh_k) = m;
+					}
+					if (tok.tokt == PPTOK_NEWLINE) goto check_next_token;
+					else goto start_cur_token;
+				}
+			} else if (!strcmp(string_content(tok.tokv.str), "undef")) {
+				string_del(tok.tokv.str);
+				tok = ppsrc_next_token(src);
+				if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) {
+					printf("Invalid token type %u after '#undef' preprocessor command\n", tok.tokt);
+					goto preproc_hash_err;
+				}
+				string_t *mname = tok.tokv.str;
+				tok = ppsrc_next_token(src); // Token was moved
+				while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) {
+					// TODO: Print warning 'ignored token(s)'
+					preproc_token_del(&tok);
+					tok = ppsrc_next_token(src);
+				}
+				khiter_t it = kh_get(macros_map, src->macros_map, string_content(mname));
+				if (it != kh_end(src->macros_map)) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+					free((void*)kh_key(src->macros_map, it));
+#pragma GCC diagnostic pop
+					macro_del(&kh_val(src->macros_map, it));
+					kh_del(macros_map, src->macros_map, it);
+				}
+				string_del(mname);
+				if (tok.tokt == PPTOK_NEWLINE) goto check_next_token;
+				else goto start_cur_token;
+			} else if (!strcmp(string_content(tok.tokv.str), "error")) {
+				printf("Error: #error command <TODO: add reason>\n");
+				string_del(tok.tokv.str);
+				vector_clear(ppsource, src->prep);
+				goto check_next_token; // Returns EOF
+			} else if (!strcmp(string_content(tok.tokv.str), "warning")) {
+				printf("Warning: #warning command <TODO: add reason>\n");
+				goto preproc_hash_err;
+			} else if (!strcmp(string_content(tok.tokv.str), "pragma")) {
+				string_del(tok.tokv.str);
+				tok = ppsrc_next_token(src);
+				if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) {
+					printf("Unknown pragma directive, skipping until EOL\n");
+					goto preproc_hash_err;
+				} else if (!strcmp(string_content(tok.tokv.str), "wrappers")) {
+					string_del(tok.tokv.str);
+					tok = ppsrc_next_token(src);
+					if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) {
+						printf("Unknown pragma wrappers directive, skipping until EOL\n");
+						goto preproc_hash_err;
+					} else if (!strcmp(string_content(tok.tokv.str), "allow_ints_ext")) {
+						while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) {
+							preproc_token_del(&tok);
+							tok = ppsrc_next_token(src);
+						}
+						if (tok.tokt == PPTOK_INVALID) goto start_cur_token;
+						else {
+							ret.tokt = PTOK_PRAGMA;
+							ret.tokv.pragma.typ = PRAGMA_ALLOW_INTS;
+							return ret;
+						}
+					} else if (!strcmp(string_content(tok.tokv.str), "explicit_simple")) {
+						string_del(tok.tokv.str);
+						tok = ppsrc_next_token(src);
+						if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) {
+							printf("Invalid pragma wrappers explicit_simple directive, skipping until EOL\n");
+							goto preproc_hash_err;
+						}
+						string_t *id = tok.tokv.str;
+						tok = ppsrc_next_token(src);
+						while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) {
+							preproc_token_del(&tok);
+							tok = ppsrc_next_token(src);
+						}
+						if (tok.tokt == PPTOK_INVALID) {
+							string_del(id);
+							goto start_cur_token;
+						} else {
+							ret.tokt = PTOK_PRAGMA;
+							ret.tokv.pragma.typ = PRAGMA_MARK_SIMPLE;
+							ret.tokv.pragma.val = id;
+							return ret;
+						}
+					} else {
+						printf("Unknown pragma wrappers directive '%s', skipping until EOL\n", string_content(tok.tokv.str));
+						goto preproc_hash_err;
+					}
+				} else {
+					printf("Unknown pragma directive '%s', skipping until EOL\n", string_content(tok.tokv.str));
+					goto preproc_hash_err;
+				}
+			} else if (!strcmp(string_content(tok.tokv.str), "if")) {
+				if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) {
+					printf("Error: invalid #if source type %u\n", vector_last(ppsource, src->prep).srct);
+					src->st = PPST_NONE;
+					ret.tokt = PTOK_INVALID;
+					ret.tokv = (union proc_token_val_u){.c = '\0'};
+					return ret;
+				}
+				string_del(tok.tokv.str);
+				VECTOR(preproc) *cond = vector_new(preproc);
+				if (!cond) {
+					printf("Error: failed to allocate #if condition vector\n");
+					src->st = PPST_NONE;
+					ret.tokt = PTOK_INVALID;
+					ret.tokv = (union proc_token_val_u){.c = '\0'};
+					return ret;
+				}
+				tok = ppsrc_next_token(src);
+				while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) {
+					if (!vector_push(preproc, cond, tok)) {
+						printf("Error: failed to add token to #if condition vector\n");
+						vector_del(preproc, cond);
+						src->st = PPST_NONE;
+						ret.tokt = PTOK_INVALID;
+						ret.tokv = (union proc_token_val_u){.c = '\0'};
+						return ret;
+					}
+					tok = ppsrc_next_token(src);
+				}
+				vector_trim(preproc, cond);
+				khash_t(string_set) *solved_macros = kh_init(string_set);
+				if (!solved_macros) {
+					printf("Error: failed to allocate #if solved_macros set\n");
+					vector_del(preproc, cond);
+					src->st = PPST_NONE;
+					ret.tokt = PTOK_INVALID;
+					ret.tokv = (union proc_token_val_u){.c = '\0'};
+					return ret;
+				}
+				
+				VECTOR(preproc) *expanded = proc_do_expand(src->macros_map, cond, solved_macros, src->macros_used);
+				vector_del(preproc, cond);
+				macros_set_del(solved_macros);
+				if (!expanded) {
+					printf("Error: failed to expand #if condition\n");
+					src->st = PPST_NONE;
+					ret.tokt = PTOK_INVALID;
+					ret.tokv = (union proc_token_val_u){.c = '\0'};
+					return ret;
+				}
+				
+				// Now we need to compute what is pointed by expanded, and increase cond_depth and ok_depth as needed
+				int st;
+				int64_t res = preproc_eval(expanded, &st);
+				vector_del(preproc, expanded);
+				if (!st) {
+					printf("Error: failed to evaluate #if condition (%s)\n", src->cur_file);
+					src->st = PPST_NONE;
+					ret.tokt = PTOK_INVALID;
+					ret.tokv = (union proc_token_val_u){.c = '\0'};
+					return ret;
+				}
+				++vector_last(ppsource, src->prep).srcv.prep.cond_depth;
+				if (res) {
+					vector_last(ppsource, src->prep).srcv.prep.entered_next_ok_cond = 1;
+					++vector_last(ppsource, src->prep).srcv.prep.ok_depth;
+					if (tok.tokt == PPTOK_NEWLINE) goto check_next_token;
+					else goto start_cur_token;
+				} else {
+					vector_last(ppsource, src->prep).srcv.prep.entered_next_ok_cond = 0;
+					if (tok.tokt == PPTOK_NEWLINE) goto check_if_depth;
+					else goto start_cur_token; // Returns immediately
+				}
+			} else if (!strcmp(string_content(tok.tokv.str), "ifdef")) {
+				if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) {
+					printf("Error: invalid #ifdef source type %u\n", vector_last(ppsource, src->prep).srct);
+					goto preproc_hash_err;
+				}
+				string_del(tok.tokv.str);
+				tok = ppsrc_next_token(src);
+				if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) {
+					printf("Invalid token type %u after '#ifdef' preprocessor command\n", tok.tokt);
+					goto preproc_hash_err;
+				}
+				khiter_t it = kh_get(macros_map, src->macros_map, string_content(tok.tokv.str));
+				int iret;
+				char *mname = string_steal(tok.tokv.str);
+				kh_put(string_set, src->macros_used, mname, &iret);
+				if (iret <= 0) {
+					free(mname);
+				}
+				tok.tokt = PPTOK_SYM;
+				tok.tokv.sym = LAST_SYM;
+				// We don't care about iret(?)
+				// TODO: check iret, error if needed
+				++vector_last(ppsource, src->prep).srcv.prep.cond_depth;
+				if (it != kh_end(src->macros_map)) {
+					vector_last(ppsource, src->prep).srcv.prep.entered_next_ok_cond = 1;
+					++vector_last(ppsource, src->prep).srcv.prep.ok_depth;
+					goto preproc_hash_err;
+				} else {
+					vector_last(ppsource, src->prep).srcv.prep.entered_next_ok_cond = 0;
+					goto preproc_hash_err_goto;
+				}
+			} else if (!strcmp(string_content(tok.tokv.str), "ifndef")) {
+				if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) {
+					printf("Error: invalid #ifndef source type %u\n", vector_last(ppsource, src->prep).srct);
+					goto preproc_hash_err;
+				}
+				string_del(tok.tokv.str);
+				tok = ppsrc_next_token(src);
+				if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) {
+					printf("Invalid token type %u after '#ifndef' preprocessor command\n", tok.tokt);
+					goto preproc_hash_err;
+				}
+				khiter_t it = kh_get(macros_map, src->macros_map, string_content(tok.tokv.str));
+				int iret;
+				char *mname = string_steal(tok.tokv.str);
+				kh_put(string_set, src->macros_used, mname, &iret);
+				if (iret <= 0) {
+					free(mname);
+				}
+				tok.tokt = PPTOK_SYM;
+				tok.tokv.sym = LAST_SYM;
+				// We don't care about iret(?)
+				// TODO: check iret, error if needed
+				++vector_last(ppsource, src->prep).srcv.prep.cond_depth;
+				if (it == kh_end(src->macros_map)) {
+					vector_last(ppsource, src->prep).srcv.prep.entered_next_ok_cond = 1;
+					++vector_last(ppsource, src->prep).srcv.prep.ok_depth;
+					goto preproc_hash_err;
+				} else {
+					vector_last(ppsource, src->prep).srcv.prep.entered_next_ok_cond = 0;
+					goto preproc_hash_err_goto;
+				}
+			} else if (!strcmp(string_content(tok.tokv.str), "elif")) {
+				// We are already in an #if or #elif (or #else) with a match, so we need to leave it and go to the very top
+				if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) {
+					printf("Error: invalid #elif source type %u\n", vector_last(ppsource, src->prep).srct);
+					goto preproc_hash_err;
+				}
+				if (vector_last(ppsource, src->prep).srcv.prep.ok_depth) {
+					--vector_last(ppsource, src->prep).srcv.prep.ok_depth;
+					goto preproc_hash_err_goto;
+				} else {
+					printf("Warning: unexpected #elif preprocessor command\n");
+					goto preproc_hash_err;
+				}
+			} else if (!strcmp(string_content(tok.tokv.str), "elifdef")) {
+				// We are already in an #if or #elif (or #else) with a match, so we need to leave it and go to the very top
+				if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) {
+					printf("Error: invalid #elifdef source type %u\n", vector_last(ppsource, src->prep).srct);
+					goto preproc_hash_err;
+				}
+				string_del(tok.tokv.str);
+				tok = ppsrc_next_token(src);
+				int iret;
+				char *mname = string_steal(tok.tokv.str);
+				kh_put(string_set, src->macros_used, mname, &iret);
+				if (iret <= 0) {
+					free(mname);
+				}
+				tok.tokt = PPTOK_SYM;
+				tok.tokv.sym = LAST_SYM;
+				// We don't care about iret(?)
+				// TODO: check iret, error if needed
+				if (vector_last(ppsource, src->prep).srcv.prep.ok_depth) {
+					--vector_last(ppsource, src->prep).srcv.prep.ok_depth;
+					goto preproc_hash_err_goto;
+				} else {
+					printf("Warning: unexpected #elifdef preprocessor command\n");
+					goto preproc_hash_err;
+				}
+			} else if (!strcmp(string_content(tok.tokv.str), "elifndef")) {
+				// We are already in an #if or #elif (or #else) with a match, so we need to leave it and go to the very top
+				if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) {
+					printf("Error: invalid #elifndef source type %u\n", vector_last(ppsource, src->prep).srct);
+					goto preproc_hash_err;
+				}
+				string_del(tok.tokv.str);
+				tok = ppsrc_next_token(src);
+				int iret;
+				char *mname = string_steal(tok.tokv.str);
+				kh_put(string_set, src->macros_used, mname, &iret);
+				if (iret <= 0) {
+					free(mname);
+				}
+				tok.tokt = PPTOK_SYM;
+				tok.tokv.sym = LAST_SYM;
+				// We don't care about iret(?)
+				// TODO: check iret, error if needed
+				if (vector_last(ppsource, src->prep).srcv.prep.ok_depth) {
+					--vector_last(ppsource, src->prep).srcv.prep.ok_depth;
+					goto preproc_hash_err_goto;
+				} else {
+					printf("Warning: unexpected #elifndef preprocessor command\n");
+					goto preproc_hash_err;
+				}
+			} else if (!strcmp(string_content(tok.tokv.str), "else")) {
+				// We are already in an #if or #elif (or #else) with a match, so we need to leave it and go to the very top
+				if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) {
+					printf("Error: invalid #else source type %u\n", vector_last(ppsource, src->prep).srct);
+					goto preproc_hash_err;
+				}
+				if (vector_last(ppsource, src->prep).srcv.prep.ok_depth) {
+					--vector_last(ppsource, src->prep).srcv.prep.ok_depth;
+					goto preproc_hash_err_goto;
+				} else {
+					printf("Warning: unexpected #else preprocessor command\n");
+					goto preproc_hash_err;
+				}
+			} else if (!strcmp(string_content(tok.tokv.str), "endif")) {
+				if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) {
+					printf("Error: invalid #endif source type %u\n", vector_last(ppsource, src->prep).srct);
+					goto preproc_hash_err;
+				}
+				if (vector_last(ppsource, src->prep).srcv.prep.ok_depth) {
+					--vector_last(ppsource, src->prep).srcv.prep.ok_depth;
+					--vector_last(ppsource, src->prep).srcv.prep.cond_depth;
+				} else {
+					printf("Warning: unexpected #endif preprocessor command\n");
+				}
+				goto preproc_hash_err;
+			}
+			
+			printf("Unknown pp command %s (%s), skipping until EOL\n", string_content(tok.tokv.str), src->cur_file);
+		preproc_hash_err:
+			while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) {
+				preproc_token_del(&tok);
+				tok = ppsrc_next_token(src);
+			}
+			if (tok.tokt == PPTOK_NEWLINE) goto check_next_token;
+			else goto start_cur_token;
+			
+		preproc_hash_err_goto:
+			while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) {
+				preproc_token_del(&tok);
+				tok = ppsrc_next_token(src);
+			}
+			if (tok.tokt == PPTOK_NEWLINE) goto check_if_depth;
+			else goto start_cur_token; // Returns immediately
+		}
+		src->st = PPST_NONE;
+		ret.tokt = PTOK_SYM;
+		ret.tokv = (union proc_token_val_u){.sym = tok.tokv.sym};
+		return ret;
+	case PPTOK_NEWLINE:
+		src->st = PPST_NL;
+		goto check_next_token;
+	case PPTOK_BLANK:
+		src->st = PPST_NONE;
+		ret.tokt = PTOK_INVALID;
+		ret.tokv = (union proc_token_val_u){.c = tok.tokv.c};
+		return ret;
+	case PPTOK_START_LINE_COMMENT:
+		src->st = PPST_NONE;
+		ret.tokt = PTOK_INVALID;
+		ret.tokv = (union proc_token_val_u){.c = tok.tokv.c};
+		return ret;
+	case PPTOK_EOF:
+		if ((vector_last(ppsource, src->prep).srct == PPSRC_PREPARE) && vector_last(ppsource, src->prep).srcv.prep.cond_depth) {
+			printf("Error: file ended before closing all conditionals (ignoring)\n");
+		}
+		if (vector_last(ppsource, src->prep).srct == PPSRC_PREPARE) {
+			if (src->dirname) free(src->dirname);
+			if (src->cur_file) free(src->cur_file);
+			src->dirname = vector_last(ppsource, src->prep).srcv.prep.old_dirname;
+			src->cur_file = vector_last(ppsource, src->prep).srcv.prep.old_filename;
+			src->is_sys = vector_last(ppsource, src->prep).srcv.prep.was_sys;
+			src->cur_pathno = vector_last(ppsource, src->prep).srcv.prep.old_pathno;
+			vector_last(ppsource, src->prep).srcv.prep.old_dirname = NULL;
+			vector_last(ppsource, src->prep).srcv.prep.old_filename = NULL;
+		}
+		vector_pop(ppsource, src->prep);
+		src->st = PPST_NL; // Should be redundant since TOK_NEWLINE is added before TOK_EOF if required
+		// EOF has an empty destructor
+		// Note that since we have opened the file, the previous file also had ok_depth == cond_depth
+		goto check_next_token;
+	
+	default:
+		printf("Unknown next pp token type %u, sending EOF\n", tok.tokt);
+		ret.tokt = PTOK_EOF;
+		ret.tokv = (union proc_token_val_u){.c = (char)EOF};
+		return ret;
+	}
+}
+proc_token_t proc_next_token(preproc_t *src) {
+	proc_token_t ret = proc_next_token_aux(src);
+	if ((ret.tokt == PTOK_STRING) && ret.tokv.sisstr) {
+		while (1) {
+			proc_token_t ret2 = proc_next_token_aux(src);
+			if ((ret.tokt == PTOK_STRING) && ret.tokv.sisstr) {
+				if (!string_add_string(ret.tokv.sstr, ret2.tokv.sstr)) {
+					printf("Error: failed to concatenate adjacent strings\n");
+					string_del(ret.tokv.sstr);
+					string_del(ret2.tokv.sstr);
+					src->st = PPST_NONE;
+					ret.tokt = PTOK_INVALID;
+					ret.tokv = (union proc_token_val_u){.c = '\0'};
+					return ret;
+				}
+				string_del(ret2.tokv.sstr);
+			} else {
+				if (!proc_unget_token(src, &ret2)) {
+					printf("Error: failed to unget token next to string token\n");
+					string_del(ret.tokv.sstr);
+					proc_token_del(&ret2);
+					src->st = PPST_NONE;
+					ret.tokt = PTOK_INVALID;
+					ret.tokv = (union proc_token_val_u){.c = '\0'};
+					return ret;
+				}
+				return ret;
+			}
+		}
+	} else return ret;
+}
diff --git a/wrapperhelper/src/preproc.h b/wrapperhelper/src/preproc.h
new file mode 100644
index 00000000..3ba2e06c
--- /dev/null
+++ b/wrapperhelper/src/preproc.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#ifndef PREPROC_H
+#define PREPROC_H
+
+#include <stdio.h>
+
+#include "lang.h"
+
+typedef struct preproc_s preproc_t;
+
+preproc_t *preproc_new_file(FILE *f, char *dirname, const char *filename); // Takes ownership of f and dirname
+proc_token_t proc_next_token(preproc_t *src);
+int proc_unget_token(preproc_t *src, proc_token_t *tok);
+void preproc_del(preproc_t *src);
+
+#endif // PREPROC_H
diff --git a/wrapperhelper/src/vector.c b/wrapperhelper/src/vector.c
new file mode 100644
index 00000000..2cccf09d
--- /dev/null
+++ b/wrapperhelper/src/vector.c
@@ -0,0 +1,105 @@
+#include "vector.h"
+
+VECTOR_IMPL(voidp, (void))
+VECTOR_IMPL(char, (void))
+VECTOR_IMPL(charp, (void))
+static void stringp_del(string_t **s) { return string_del(*s); }
+VECTOR_IMPL(string, stringp_del)
+
+VECTOR(voidp) *vector_new_impl(void) {
+	VECTOR(voidp) *ret = malloc(sizeof(*ret));
+	if (!ret) return NULL;
+	ret->vsize = ret->vcap = 0; ret->content_v = NULL;
+	return ret;
+}
+
+VECTOR(voidp) *vector_new_cap_impl(size_t elem_size, size_t cap) {
+	VECTOR(voidp) *ret = malloc(sizeof(*ret));
+	if (!ret) return NULL;
+	cap = (cap < VECTOR_MIN_CAP) ? VECTOR_MIN_CAP : cap * 2;
+	ret->content_v = malloc(cap * elem_size);
+	if (!ret->content_v) {
+		free(ret);
+		return NULL;
+	}
+	ret->vcap = cap;
+	ret->vsize = 0;
+	return ret;
+}
+
+int vector_reserve_impl(VECTOR(voidp) *v, size_t elem_size, size_t cap) {
+	size_t new_cap = (cap < VECTOR_MIN_CAP) ? VECTOR_MIN_CAP : cap;
+	if (new_cap <= v->vcap) return 1;
+	
+	void *new_content_v = realloc(v->content_v, elem_size * new_cap);
+	if (!new_content_v) return 0;
+	v->content_v = new_content_v;
+	v->vcap = new_cap;
+	return 1;
+}
+
+int vector_trim_impl(VECTOR(voidp) *v, size_t elem_size) {
+	if (v->vsize) {
+		void *new_content_v = realloc(v->content_v, elem_size * v->vsize);
+		if (!new_content_v) return 0;
+		v->content_v = new_content_v;
+		v->vcap = v->vsize;
+		return 1;
+	} else {
+		free(v->content_v);
+		v->content_v = NULL;
+		v->vcap = 0;
+		return 1;
+	}
+}
+
+void vector_common_pop_impl(VECTOR(voidp) *v, size_t elem_size) {
+	if (--v->vsize < v->vcap / 4) {
+		size_t new_cap = (v->vcap / 2 < VECTOR_MIN_CAP) ? VECTOR_MIN_CAP : v->vcap / 2;
+		if (new_cap == v->vcap) return;
+		void *new_content_v = realloc(v->content_v, elem_size * new_cap);
+		if (!new_content_v) return; // We don't really care if the realloc fails, we just need to not update anything
+		v->content_v = new_content_v;
+		v->vcap = new_cap;
+	}
+}
+
+void vector_common_popn_impl(VECTOR(voidp) *v, size_t n, size_t elem_size) {
+	if (n > v->vsize) n = v->vsize;
+	v->vsize -= n;
+	if (v->vsize < v->vcap / 4) {
+		size_t new_cap = v->vcap / 2;
+		while (v->vsize < new_cap / 4) new_cap /= 2;
+		new_cap = (new_cap < VECTOR_MIN_CAP) ? VECTOR_MIN_CAP : new_cap;
+		if (new_cap == v->vcap) return;
+		void *new_content_v = realloc(v->content_v, elem_size * new_cap);
+		if (!new_content_v) return; // We don't really care if the realloc fails, we just need to not update anything
+		v->content_v = new_content_v;
+		v->vcap = new_cap;
+	}
+}
+
+void vector_common_clear_impl(VECTOR(voidp) *v) {
+	if (!v->content_v) return;
+	free(v->content_v);
+	v->vsize = v->vcap = 0; v->content_v = NULL;
+}
+
+int vector_push_vec_impl(VECTOR(voidp) *v1, VECTOR(voidp) *v2, size_t start, size_t len, size_t elem_size) {
+	if (start >= v2->vsize) return 1;
+	if (start + len > v2->vsize) len = v2->vsize - start;
+	if (!len) return 1;
+	if (v1->vsize + len > v1->vcap) {
+		size_t new_cap = (v1->vcap < VECTOR_MIN_CAP) ? VECTOR_MIN_CAP : v1->vcap * 2;
+		while (v1->vsize + len > new_cap) {
+			new_cap = new_cap * 2;
+		}
+		void *new_content_v = realloc(v1->content_v, elem_size * new_cap);
+		if (!new_content_v) return 0;
+		v1->content_v = new_content_v;
+		v1->vcap = new_cap;
+	}
+	memcpy((char*)v1->content_v + elem_size * v1->vsize, (char*)v2->content_v + elem_size * start, elem_size * len);
+	v1->vsize += len;
+	return 1;
+}
diff --git a/wrapperhelper/src/vector.h b/wrapperhelper/src/vector.h
new file mode 100644
index 00000000..70319cc2
--- /dev/null
+++ b/wrapperhelper/src/vector.h
@@ -0,0 +1,213 @@
+#pragma once
+
+#ifndef VECTOR_H
+#define VECTOR_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cstring.h"
+
+// Note: do not use with empty types; simply use ints in this case.
+
+/** Thread-unsafe vector implementation
+ * USAGE:
+ * ======
+ * VECTOR_DECLARE ----------- Declare a new vector type. Takes the name and the element type.
+ * VECTOR_IMPL -------------- Implements required functions for a vector type. Takes the name and the destructor of a pointer to the element.
+ * VECTOR_DECLARE_STATIC ---- Declare a new vector type with static implementation. Takes the name and the element type.
+ * VECTOR_IMPL_STATIC ------- Implements required functions for a vector type. Takes the name and the destructor of a pointer to the element.
+ *                                Functions are declared static.
+ * VECTOR_DEL --------------- Macro that takes the vector name and gives a function taking a vector and deletes it.
+ * VECTOR ------------------- The vector type. Takes the name.
+ * VECTOR_ELEM -------------- The element type. Takes the name.
+ * vector_new --------------- Creates a new vector. Takes the name.
+ * vector_new_cap ----------- Creates a new vector with a given capacity. Takes the name and the capacity.
+ * vector_reserve ----------- Ensures a vector has at least a given capacity. Takes the name, the vector and the capacity. May not reduce the vector capacity.
+ * vector_trim -------------- Ensures a vector has a capacity equal to its size. Takes the name, the vector. May reduce the vector capacity.
+ * vector_del --------------- Frees a vector. Takes the name and the vector. Destructs the content of the vector.
+ * vector_steal ------------- Frees a vector and returns the content. Takes the name and the vector. May reduce the vector capacity.
+ * vector_del_freed --------- Frees a vector without freeing the content. Takes the name and the vector. Does not interact with the content of the vector.
+ * vector_del_free_from ----- Frees a vector without freeing all of the content. Takes the name, the vector and the first element to free.
+ *                                Destroys part of the content of the vector.
+ * vector_push -------------- Push a new element. Takes the name, the vector and the new element. Does not fail if enough capacity remains.
+ * vector_push_vec ---------- Push a vector of new elements. Takes the name, the vector and the new element vector. Does not fail if enough capacity remains.
+ * vector_push_vec_slice ---- Push a slice of a vector of elements. Takes the name, the vector the new element vector, start and size.
+ *                                Does not fail if enough capacity remains.
+ * vector_pop --------------- Pops the last element. Takes the name and the vector. May reduce the vector capacity.
+ * vector_pop_nodel --------- Pops the last element without freeing it. Takes the name and the vector. May reduce the vector capacity.
+ * vector_pop_slice --------- Pops the last N element. Takes the name, the vector and the number of elements to remove.
+ * vector_pop_nodel_slice --- Pops the last N element without freeing them. Takes the name, the vector and the number of elements to remove.
+ *                                May reduce the vector capacity.
+ * vector_clear ------------- Remove every element. Takes the name and the vector.
+ * vector_clear_nodel ------- Remove every element without freeing them. Takes the name and the vector.
+ * vector_remove ------------ Removes an element. Takes the name, the vector and the element number. May reduce the vector capacity.
+ * vector_size -------------- Vector size (number of elements). Takes the name and the vector.
+ * vector_cap --------------- Vector capacity (number of elements). Takes the name and the vector.
+ * vector_content ----------- Pointer to the vector content. Takes the name and the vector.
+ * vector_begin ------------- Start of the vector content. Takes the name and the vector.
+ * vector_end --------------- End of the vector content. Points to unmanaged memory. Takes the name and the vector.
+ * vector_last -------------- Last element of the vector. Points to invalid memory if size is zero. Takes the name and the vector.
+ * vector_for --------------- Iterate over the elements of a vector. This is a for loop. Takes the name, the iterator name and the vector.
+ * 
+ * VECTOR_DEL(name)(v) is equivalent to vector_del(name, v).
+ * Predefined vectors: string (string_t*), char (char), charp (char*), voidp (void*)
+ * 
+ * EXAMPLE:
+ * ========
+ * Header myvec.h:
+ * ---------------
+// ...
+VECTOR_DECLARE(myvec, my_elem_t*)
+// ...
+
+ * Source myvec.c:
+ * ---------------
+// ...
+VECTOR_IMPL(myvec)
+// ...
+
+ * Source main.c:
+ * -------------------
+// ...
+extern my_elem_t elems[2];
+int main() {
+	VECTOR(myvec) *vec = vector_new_cap(myvec, 2);
+	vector_push(myvec, vec, &elems[0]);
+	vector_push(myvec, vec, &elems[1]);
+	vector_for (myvec, it, vec) {
+		printf("We have an element: %s\n", it->elem_name);
+	}
+	vector_del(myvec, vec);
+}
+ */
+
+#define VECTOR(name) vector_##name##_t
+#define VECTOR_ELEM(name) vector_##name##_elem
+
+#define VECTOR_MIN_CAP 8
+
+#define VECTOR_DECLARE_(name, t, pre) \
+	typedef struct vector_##name##_s {                                        \
+		size_t vsize;                                                         \
+		size_t vcap;                                                          \
+		union {                                                               \
+			t *content;                                                       \
+			void *content_v;                                                  \
+		};                                                                    \
+	} VECTOR(name);                                                           \
+	typedef t VECTOR_ELEM(name);                                              \
+	pre VECTOR_ELEM(name) *vector_steal_##name(VECTOR(name) *v);              \
+	pre void vector_pop_del_##name(VECTOR(name) *v);                          \
+	pre void vector_popn_del_##name(VECTOR(name) *v, size_t idx);             \
+	pre void vector_clear_del_##name(VECTOR(name) *v);                        \
+	pre void vector_del_from_##name(VECTOR(name) *v, size_t idx);             \
+	pre int vector_push_elem_##name(VECTOR(name) *v, VECTOR_ELEM(name) elem);
+#define VECTOR_DECLARE(name, t) VECTOR_DECLARE_(name, t,)
+#define VECTOR_DECLARE_STATIC(name, t) VECTOR_DECLARE_(name, t, static)
+
+VECTOR_DECLARE(voidp, void*)
+
+VECTOR(voidp) *vector_new_impl(void);
+VECTOR(voidp) *vector_new_cap_impl(size_t elem_size, size_t cap);
+int            vector_reserve_impl(VECTOR(voidp) *v, size_t elem_size, size_t cap);
+int            vector_trim_impl(VECTOR(voidp) *v, size_t elem_size);
+void           vector_common_pop_impl(VECTOR(voidp) *v, size_t elem_size);
+void           vector_common_popn_impl(VECTOR(voidp) *v, size_t n, size_t elem_size);
+void           vector_common_clear_impl(VECTOR(voidp) *v);
+int            vector_push_vec_impl(VECTOR(voidp) *v1, VECTOR(voidp) *v2, size_t start, size_t len, size_t elem_size);
+
+#define vector_new(name) (VECTOR(name)*)vector_new_impl()
+#define vector_new_cap(name, cap) (VECTOR(name)*)vector_new_cap_impl(sizeof(VECTOR_ELEM(name)), (cap))
+#define vector_del(name, v) vector_del_from_##name((v), 0)
+#define vector_del_freed(name, v) vector_del_from_##name((v), vector_size(name, (v)))
+#define vector_del_free_from(name, v, i) vector_del_from_##name((v), (i))
+#define VECTOR_DEL(name) vector_del_##name
+#define vector_steal(name, v) vector_steal_##name((v))
+#define vector_reserve(name, v, cap) vector_reserve_impl((VECTOR(voidp)*)(v), sizeof(VECTOR_ELEM(name)), (cap))
+#define vector_trim(name, v) vector_trim_impl((VECTOR(voidp)*)(v), sizeof(VECTOR_ELEM(name)))
+#define vector_push(name, v, e) vector_push_elem_##name((v), (e))
+#define vector_push_vec(name, v1, v2) vector_push_vec_impl((VECTOR(voidp)*)(v1), (VECTOR(voidp)*)(v2), 0, vector_size(name, (v2)), sizeof(VECTOR_ELEM(name)))
+#define vector_push_vec_slice(name, v1, v2, s, l) vector_push_vec_impl((VECTOR(voidp)*)(v1), (VECTOR(voidp)*)(v2), (s), (l), sizeof(VECTOR_ELEM(name)))
+#define vector_pop(name, v) vector_pop_del_##name((v))
+#define vector_pop_slice(name, v, n) vector_popn_del_##name((VECTOR(voidp)*)(v), (n))
+#define vector_pop_nodel(name, v) vector_common_pop_impl((VECTOR(voidp)*)(v), sizeof(VECTOR_ELEM(name)))
+#define vector_pop_nodel_slice(name, v, n) vector_common_popn_impl((VECTOR(voidp)*)(v), (n), sizeof(VECTOR_ELEM(name)))
+#define vector_clear(name, v) vector_clear_del_##name((v))
+#define vector_clear_nodel(name, v) vector_common_clear_impl((VECTOR(voidp)*)(v))
+#define vector_remove(name, v, i) vector_remove_##name((v), (i))
+
+#define vector_size(name, v) ((v)->vsize)
+#define vector_cap(name, v) ((v)->vcap)
+#define vector_content(name, v) ((v)->content)
+#define vector_begin(name, v) ((v)->content)
+#define vector_end(name, v) ((v)->content + (v)->vsize)
+#define vector_last(name, v) ((v)->content[(v)->vsize - 1])
+#define vector_for(name, itname, v) \
+	for (VECTOR_ELEM(name) *itname = vector_begin(name, (v)); itname < vector_end(name, (v)); ++itname)
+#define vector_for_from(name, itname, v, i) \
+	for (VECTOR_ELEM(name) *itname = vector_begin(name, (v)) + (i); itname < vector_end(name, (v)); ++itname)
+#define vector_for_rev(name, itname, v) \
+	for (VECTOR_ELEM(name) *itname = (v)->content ? vector_end(name, (v)) - 1 : NULL; (v)->content && (itname >= vector_begin(name, (v))); --itname)
+
+#define VECTOR_IMPL_(name, dtor, pre) \
+	pre VECTOR_ELEM(name) *vector_steal_##name(VECTOR(name) *v) {                                      \
+		vector_trim(name, v);                                                                          \
+		VECTOR_ELEM(name) *ret = v->content;                                                           \
+		free(v);                                                                                       \
+		return ret;                                                                                    \
+	}                                                                                                  \
+	                                                                                                   \
+	pre void vector_pop_del_##name(VECTOR(name) *v) {                                                  \
+		if (v->vsize) {                                                                                \
+			dtor(&vector_last(name, v));                                                               \
+			vector_common_pop_impl((VECTOR(voidp)*)v, sizeof(VECTOR_ELEM(name)));                      \
+		}                                                                                              \
+	}                                                                                                  \
+	pre void vector_popn_del_##name(VECTOR(name) *v, size_t n) {                                       \
+		if (v->vsize > n) n = v->vsize;                                                                \
+		vector_for_from(name, it, v, v->vsize - n) { dtor(it); }                                       \
+		vector_common_popn_impl((VECTOR(voidp)*)v, n, sizeof(VECTOR_ELEM(name)));                      \
+	}                                                                                                  \
+	                                                                                                   \
+	pre void vector_remove_##name(VECTOR(name) *v, size_t i) {                                         \
+		dtor(v->content + i);                                                                          \
+	    memmove(v->content + i, v->content + i + 1, (v->vsize - i - 1) * sizeof(VECTOR_ELEM(name)));   \
+	    vector_common_pop_impl((VECTOR(voidp)*)v, sizeof(VECTOR_ELEM(name)));                          \
+	}                                                                                                  \
+	                                                                                                   \
+	pre void vector_clear_del_##name(VECTOR(name) *v) {                                                \
+		if (!v->content) return;                                                                       \
+		vector_for(name, it, v) { dtor(it); }                                                          \
+		free(v->content);                                                                              \
+		v->content = NULL;                                                                             \
+		v->vcap = v->vsize = 0;                                                                        \
+	}                                                                                                  \
+	                                                                                                   \
+	pre void vector_del_from_##name(VECTOR(name) *v, size_t idx) {                                     \
+		vector_for_from(name, it, v, idx) { dtor(it); }                                                \
+		if (v->content) free(v->content);                                                              \
+		free(v);                                                                                       \
+	}                                                                                                  \
+	                                                                                                   \
+	pre int vector_push_elem_##name(VECTOR(name) *v, VECTOR_ELEM(name) elem) {                         \
+		if (v->vsize >= v->vcap) {                                                                     \
+			size_t new_cap = (v->vcap < VECTOR_MIN_CAP) ? VECTOR_MIN_CAP : v->vcap * 2;                \
+			VECTOR_ELEM(name) *new_content = realloc(v->content, sizeof(VECTOR_ELEM(name)) * new_cap); \
+			if (!new_content) return 0;                                                                \
+			v->content = new_content;                                                                  \
+			v->vcap = new_cap;                                                                         \
+		}                                                                                              \
+		v->content[v->vsize++] = elem;                                                                 \
+		return 1;                                                                                      \
+	}
+#define VECTOR_IMPL(name, dtor) VECTOR_IMPL_(name, dtor,)
+#define VECTOR_IMPL_STATIC(name, dtor) VECTOR_IMPL_(name, dtor, static inline)
+
+VECTOR_DECLARE(char, char)
+VECTOR_DECLARE(charp, char*)
+VECTOR_DECLARE(string, string_t*)
+
+#endif // VECTOR_H
diff --git a/wrapperhelper/utils.h b/wrapperhelper/utils.h
deleted file mode 100644
index 06acbb72..00000000
--- a/wrapperhelper/utils.h
+++ /dev/null
@@ -1,33 +0,0 @@
-#pragma once
-#include <clang/AST/ASTContext.h>
-#include <clang/AST/Decl.h>
-#include <clang/AST/Type.h>
-#include <clang/Tooling/Tooling.h>
-#include <clang/AST/RecordLayout.h>
-#include <clang/AST/Decl.h>
-
-#include <llvm/ADT/Triple.h>
-#include <llvm/Support/Casting.h>
-
-#include <cstddef>
-#include <cstring>
-#include <iostream>
-
-static const clang::Type* StripTypedef(clang::QualType type) {
-    if (type->isTypedefNameType()) {
-        return StripTypedef(type->getAs<clang::TypedefType>()->getDecl()->getUnderlyingType());
-    } else {
-        return type.getTypePtr();
-    }
-}
-
-// FIXME: Need to support other triple except default target triple
-static std::string GetDeclHeaderFile(clang::ASTContext& Ctx, clang::Decl* Decl) {
-    const auto& SourceManager = Ctx.getSourceManager();
-    const clang::FileID FileID = SourceManager.getFileID(Decl->getBeginLoc());
-    const clang::FileEntry *FileEntry = SourceManager.getFileEntryForID(FileID);
-    if (FileEntry) {
-        return FileEntry->getName().str();
-    }
-    return "";
-}
\ No newline at end of file