Kleine Parser Vorstellung, falls es jemanden interessiert



  • Hi,

    ich habe mich am Parsing in C++ immer etwas gestört. Einerseits müssen die Objekte für operator >> default Konstruktoren haben, und zweitens sind die Streams an manchen stellen wirklich ungewöhnlich langsam. Zudem hat man kaum eine Möglichkeit eine Einfache Syntax festzulegen, was ja sogar bei scanf() kein Problem ist. Ich habe mir also einfach mal mein eigenes kleines scan() gebastelt, allerdings mit ein paar Vorteilen:

    - Typ und Parameterzahl sicher.
    - Kann Speicher für Strings automatisch reservieren.
    - Ist für jeden Datentyp erweiterbar. (Nicht durch Operatorüberladungen, sondern durch Templatespezialisierungen. Klappt aber ganz gut.)

    Das Ganze ist noch nicht wirklich ausgereift, aber die Grundidee kann man schon gut zeigen denke ich. Vielleicht inspiriert es ja den einen oder anderen es in abgewandelter Form in seinem Projekt zu nutzen.
    Das Ganze war übrigens als variadic Template geplant, da aber auch das neueste VS diese noch nicht unterstützt habe ich mal darauf verzichtet und mit Überladungen gearbeitet.
    Das Einzige, was die Typen die einzulesen sind brauchen, ist ein Movekonstruktor. Sie werden nicht kopiert, nicht zugewiesen und am wichtigsten: Sie brauchen keinen Defaultkonstruktor. (Ich weiß, man kann auch mit streams extra Funktionen erstellen, aber das passt dann so gar nicht ins Gesamtkonzept. ;))

    Anzumerken ist noch, dass das Design aus objektorientierter Sichtweise nicht ganz sauber ist, da die parse Funktion eine nonconst Referenz auf eine private Membervariable des scanners bekommt. Etwas Besseres ist mir aber nicht eingefallen.

    ideone: http://ideone.com/rX6wV

    #ifndef SCANNER_HPP
    #define SCANNER_HPP
    
    #include <cstdlib>
    #include <stdexcept>
    #include <tuple>
    #include <type_traits>
    
    template <typename T>
    struct static_parser;
    
    template <template <typename> class Parser = static_parser>
    class scanner
    {
    	const char* buf_;
    
    public:
    	scanner(const char* over)
    		: buf_(over)
    	{
    		if (buf_ == nullptr)
    			throw std::logic_error("Invalid nullpointer");
    	}
    
    	template <typename T0>
    	T0 scan(const char* format)
    	{
    		auto r = scan_impl<T0>(format);
    
    		while (*buf_ && *format)
    		{
    			if (*format == '%' && *++format != '%')
    			{
    				throw std::runtime_error("Unexpected % in format string.");
    			}
    
    			if (*buf_++ != *format++)
    				throw std::runtime_error("Format string differs from buffer.");
    		}
    
    		if (*format)
    			throw std::runtime_error("Format string is longer than buffer.");
    
    		return r;
    	}
    
    	template <typename T0, typename T1>
    	std::tuple<T0, T1> scan(const char* format)
    	{
    		auto r0 = scan_impl<T0>(format);
    		return std::make_tuple(r0, scan<T1>(format));
    	}
    
    	template <typename T0, typename T1, typename T2>
    	std::tuple<T0, T1, T2> scan(const char* format)
    	{
    		auto r0 = scan_impl<T0>(format);
    		auto t = scan<T1, T2>(format);
    		return std::make_tuple(r0, std::get<0>(t), std::get<1>(t));
    	}
    
    	template <typename T0, typename T1, typename T2, typename T3>
    	std::tuple<T0, T1, T2, T3> scan(const char* format)
    	{
    		auto r0 = scan_impl<T0>(format);
    		auto t = scan<T1, T2, T3>(format);
    		return std::make_tuple(r0, std::get<0>(t), std::get<1>(t), std::get<2>(t));
    	}
    
    	template <typename T0, typename T1, typename T2, typename T3, typename T4>
    	std::tuple<T0, T1, T2, T3, T4> scan(const char* format)
    	{
    		auto r0 = scan_impl<T0>(format);
    		auto t = scan<T1, T2, T3, T4>(format);
    		return std::make_tuple(r0, std::get<0>(t), std::get<1>(t), std::get<2>(t), std::get<3>(t));
    	}
    
    	template <typename T0, typename T1, typename T2, typename T3, typename T4, typename T5>
    	std::tuple<T0, T1, T2, T3, T4, T5> scan(const char* format)
    	{
    		auto r0 = scan_impl<T0>(format);
    		auto t = scan<T1, T2, T3, T4, T5>(format);
    		return std::make_tuple(r0, std::get<0>(t), std::get<1>(t), std::get<2>(t), std::get<3>(t), std::get<4>(t));
    	}
    
    private:
    	template <typename T0>
    	T0 scan_impl(const char*& format)
    	{
    		while (*buf_ && *format)
    		{
    			if (*format == '%' && *++format != '%')
    				return Parser<T0>::parse(*this, buf_);
    
    			if (*buf_++ != *format++)
    				throw std::runtime_error("Unexpected character.");
    		}
    		throw std::runtime_error("Unexpected end.");
    	}
    };
    
    template <typename T>
    struct static_parser
    {
    	static T parse(scanner<>& s, const char*& buf)
    	{ // dummy so the compiler can only use static_assert if function is instantiated
    		const bool is_ = std::is_pod<T>::value;
    		const bool or_ = !std::is_pod<T>::value;
    		static_assert(is_ || or_, "No parsing function for this type.");
    	}
    };
    
    template <char C>
    class delim
    {
    	std::string data_;
    
    public:
    	delim(std::string data)
    		: data_(std::move(data))
    	{}
    
    	operator std::string() const
    	{
    		return data_;
    	}
    };
    
    template <char C>
    struct static_parser<delim<C>>
    {
    	static delim<C> parse(scanner<>&, const char*& buf)
    	{
    		auto begin = buf;
    		while (*buf && *buf != C)
    			++buf;
    		return std::string(begin, buf);
    	}
    };
    
    template <>
    std::string static_parser<std::string>::parse(scanner<>&, const char*& buf)
    {
    	while (std::isspace(*buf))
    		++buf;
    
    	auto begin = buf;
    	while (*buf && !std::isspace(*buf))
    		++buf;
    
    	return std::string(begin, buf);
    }
    
    // Es fehlen noch: signed char, unsigned char, long long, unsigned long long, long double
    
    template <>
    char static_parser<char>::parse(scanner<>&, const char*& buf)
    {
    	if (*buf != '\0')
    		return *buf++;
    	throw std::runtime_error("End of buffer found, could not parse character.");
    }
    
    template <>
    long static_parser<long>::parse(scanner<>&, const char*& buf)
    {
    	char* p;
    	auto r = std::strtol(buf, &p, 10);
    	if (r == 0 && p == buf)
    		throw std::runtime_error("Failed to parse long.");
    	buf = p;
    	return r;
    }
    
    template <>
    unsigned long static_parser<unsigned long>::parse(scanner<>&, const char*& buf)
    {
    	char* p;
    	auto r = std::strtoul(buf, &p, 10);
    	if (r == 0 && p == buf)
    		throw std::runtime_error("Failed to parse unsigned long.");
    	buf = p;
    	return r;
    }
    
    template <>
    int static_parser<int>::parse(scanner<>& s, const char*& buf)
    { // Overflow?
    	return static_parser<long>::parse(s, buf);
    }
    
    template <>
    unsigned static_parser<unsigned>::parse(scanner<>& s, const char*& buf)
    { // Overflow?
    	return static_parser<unsigned long>::parse(s, buf);
    }
    
    template <>
    double static_parser<double>::parse(scanner<>&, const char*& buf)
    {
    	char* p;
    	auto r = std::strtod(buf, &p);
    	if (r == 0.0 && p == buf)
    		throw std::runtime_error("Failed to parse double.");
    	buf = p;
    	return r;
    }
    
    template <>
    float static_parser<float>::parse(scanner<>& s, const char*& buf)
    { // Overflow?
    	return static_cast<float>(static_parser<double>::parse(s, buf));
    }
    
    #endif
    
    #include <iostream>
    
    class person
    {
    	std::string name_;
    	unsigned age_;
    
    public:
    	person(std::string name, unsigned age)
    		: name_(std::move(name))
    		, age_(age)
    	{}
    
    	person(person&& p)
    		: name_(std::move(p.name_))
    		, age_(p.age_)
    	{}
    
    	const std::string& name() const
    	{
    		return name_;
    	}
    
    	unsigned age() const
    	{
    		return age_;
    	}
    
    private:
    	person(const person&);
    	person& operator = (const person&);
    };
    
    template <>
    person static_parser<person>::parse(scanner<>& s, const char*&)
    {
    	auto t = s.scan<delim<'\n'>, int>("name: %\n""age: %\n");
    	return person(std::get<0>(t), std::get<1>(t));
    }
    
    int main()
    {
    	try
    	{
    		scanner<> s("name: John Doe\n""age: 27\n");
    		auto p = s.scan<person>("%");
    		std::cout << "Hello, " << p.name() << '\n';
    		if (p.age() <= 30)
    			std::cout << "What? You are already " << p.age() << " years old? You look younger.\n";
    		else
    			std::cout << "What? You are only " << p.age() << " years old? You look older.\n";
    	}
    	catch (const std::runtime_error& e)
    	{
    		std::cerr << e.what() << '\n';
    	}
    }
    


  • Was mir auf die Schnelle auffällt ist, dass du zwar auto verwendest (C++11), aber keine Variadic Templates.
    Du beschränkst deine scan Funktion also unnötigerweise auf maximal 5 Platzhalter.



  • DrakoXP schrieb:

    Was mir auf die Schnelle auffällt ist, dass du zwar auto verwendest (C++11), aber keine Variadic Templates.
    Du beschränkst deine scan Funktion also unnötigerweise auf maximal 5 Platzhalter.

    Hast du eigentlich den Text oben gelesen? 😉



  • okay, nein :p
    gut, dann habe ich nichts weiter anzumerken, außer: nimm den GCC 🙂



  • ganz schön gut 😮


Anmelden zum Antworten