[Spice-devel] [PATCH] spice-xpi test page generator

Peter Hatina phatina at redhat.com
Wed Jun 27 05:00:48 PDT 2012


Hi,

David Jasa gave me an input to create some tool for automatic generation
of spice-xpi test page. So here we are. I tried to be as simplistic, as
possible. There is a huge context free grammar described at mozilla dev
pages, but for our purposes, it exceeds the needs (also I've found, that
there are some mistakes in the rules, that do not correspond with the
reality). The grammar has been adapted for the needs of spice-xpi IDL
file. Not to drag other dependencies for spice-xpi, I used C++ and STL
only. It could have been done by flex/lex + yacc/bison, but why to use
other deps for a simple plugin.

Please, have a look at this and give me your opinions. If we are OK,
I would like to push this to the spice-xpi (fdo) repo.

Footnote: there is a simple makefile included for local try. It will
be replaced by the build system used by spice-xpi project.

Cheers,

Peter Hatina
EMEA ENG-Desktop Development
Red Hat Czech, Brno

---
 attribute.h        |   59 +++++++
 generator.cpp      |  281 ++++++++++++++++++++++++++++++++++
 generator.h        |   59 +++++++
 main.cpp           |   47 ++++++
 makefile           |   24 +++
 method.h           |  100 ++++++++++++
 options.cpp        |   68 +++++++++
 options.h          |   42 +++++
 parser.cpp         |  430 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 parser.h           |   56 +++++++
 redirecthelper.cpp |   77 ++++++++++
 redirecthelper.h   |   44 ++++++
 scanner.cpp        |  233 ++++++++++++++++++++++++++++
 scanner.h          |   50 ++++++
 token.h            |   77 ++++++++++
 15 files changed, 1647 insertions(+)
 create mode 100644 attribute.h
 create mode 100644 generator.cpp
 create mode 100644 generator.h
 create mode 100644 main.cpp
 create mode 100644 makefile
 create mode 100644 method.h
 create mode 100644 options.cpp
 create mode 100644 options.h
 create mode 100644 parser.cpp
 create mode 100644 parser.h
 create mode 100644 redirecthelper.cpp
 create mode 100644 redirecthelper.h
 create mode 100644 scanner.cpp
 create mode 100644 scanner.h
 create mode 100644 token.h

diff --git a/attribute.h b/attribute.h
new file mode 100644
index 0000000..04dfb98
--- /dev/null
+++ b/attribute.h
@@ -0,0 +1,59 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#ifndef ATTRIBUTE_H
+#define ATTRIBUTE_H
+
+#include <string>
+#include "token.h"
+
+class Attribute
+{
+public:
+    Attribute(Token::TokenType type, const std::string &identifier, bool readonly = false):
+        m_type(type),
+        m_identifier(identifier),
+        m_readonly(readonly)
+    {}
+
+    Attribute(const Attribute &copy):
+        m_type(copy.m_type),
+        m_identifier(copy.m_identifier),
+        m_readonly(copy.m_readonly)
+    {}
+
+    ~Attribute()
+    {}
+
+    Token::TokenType getType() const { return m_type; }
+    std::string getIdentifier() const { return m_identifier; }
+
+    Attribute &operator=(const Attribute &rhs)
+    {
+        m_type = rhs.m_type;
+        m_identifier = rhs.m_identifier;
+        m_readonly = rhs.m_readonly;
+        return *this;
+    }
+
+private:
+    Token::TokenType m_type;
+    std::string m_identifier;
+    bool m_readonly;
+};
+
+#endif // ATTRIBUTE_H
diff --git a/generator.cpp b/generator.cpp
new file mode 100644
index 0000000..c15a8c0
--- /dev/null
+++ b/generator.cpp
@@ -0,0 +1,281 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <cctype>
+#include "generator.h"
+
+std::set<std::string> Generator::s_default_attributes;
+std::set<std::string> Generator::s_default_methods;
+std::map<std::string, std::string> Generator::s_default_attribute_values;
+
+Generator::Generator(const std::list<Attribute> &attributes,
+    const std::list<Method> &methods):
+    m_attributes(attributes),
+    m_methods(methods)
+{
+    init();
+}
+
+Generator::~Generator()
+{
+}
+
+void Generator::init()
+{
+    if (!s_default_attributes.empty())
+        return;
+
+    s_default_attributes.insert("hostip");
+    s_default_attributes.insert("port");
+    s_default_attributes.insert("adminconsole");
+    s_default_attributes.insert("hotkey");
+    s_default_attributes.insert("smartcard");
+    s_default_methods.insert("setlanguagestringssection");
+    s_default_methods.insert("setlanguagestringslang");
+    s_default_methods.insert("setusbfilterfilter");
+    s_default_attribute_values["adminconsole"] = " checked";
+    s_default_attribute_values["hotkey"] = " value=\"toggle-fullscreen=shift+f11," \
+        "release-cursor=shift+f12,smartcard-insert=shift+f8,smartcard-remove=shift+f9\"";
+    s_default_attribute_values["usblistenport"] = " value=\"32023\"";
+}
+
+void Generator::generate()
+{
+    generateHeader();
+    generateConnectVars();
+    generateContent();
+    generateFooter();
+}
+
+void Generator::generateHeader()
+{
+    std::cout << "<html>\n"
+              << "<head>\n"
+              << "<title>Spice-XPI test page (generated)</title>\n"
+              << "<style type=\"text/css\">\n"
+              << "caption {\n"
+              << "    text-align: left;\n"
+              << "    font-weight: bold;\n"
+              << "}\n\n"
+              << "th {\n"
+              << "    text-align: left;\n"
+              << "}\n"
+              << "</style>\n"
+              << "</head>\n\n"
+              << "<body onload=\"bodyLoad()\" onunload=\"bodyUnload()\">\n\n"
+              << "<center>\n"
+              << "<h1>SPICE xpi test page (generated)</h1>\n"
+              << "SPICE xpi test page. Disabled (greyed out) values are passed\n"
+              << "to SPICE xpi as empty variables.\n</center>\n<br/>\n\n"
+              << "<embed type=\"application/x-spice\" width=\"0\" height=\"0\" id=\"spice-xpi\"/><br/>\n\n"
+              << "<script type=\"text/javascript\">\n\n"
+              << "var embed = document.getElementById(\"spice-xpi\");\n\n"
+              << "function bodyLoad()\n{\n    log(\"Body Load\");\n};\n\n"
+              << "function bodyUnload()\n{\n    log(\"Body Unload\");\n}\n\n"
+              << "function connect()\n{\n"
+              << "    setConnectVars();\n"
+              << "    setUsbFilter();\n"
+              << "    embed.connect();\n"
+              << "    log(\"Connect: host '\" + embed.hostIP + \"', port '\" + "
+              << "embed.port\n        + \"', secure port '\" + embed.SecurePort + "
+              << "\"', USB port '\" +\n        embed.UsbListenPort + \"'\");\n}\n\n"
+              << "function disconnect()\n{\n"
+              << "    embed.disconnect();\n"
+              << "    log(\"Disconnect\");\n}\n\n"
+              << "function OnDisconnected(msg)\n{\n    log(\"Disconnected, return code: \" + msg);\n}\n\n"
+              << "function log(message)\n{\n"
+              << "    var log = document.getElementById(\"log\");\n"
+              << "    var ts = new Date().toString() + \": \";\n"
+              << "    var newRow = document.createElement(\"tr\");\n"
+              << "    var tsCell = document.createElement(\"td\");\n"
+              << "    var msgCell = document.createElement(\"td\");\n\n"
+              << "    tsCell.innerHTML = ts;\n"
+              << "    msgCell.innerHTML = message;\n\n"
+              << "    newRow.appendChild(tsCell);\n"
+              << "    newRow.appendChild(msgCell);\n"
+              << "    log.appendChild(newRow);\n}\n\n"
+              << "function setLanguageStrings()\n{\n"
+              << "    section = document.getElementById(\"SetLanguageStringssectionToggled\").checked ?\n"
+              << "        document.getElementById(\"SetLanguageStringssection\").value : \"\";\n"
+              << "    lang = document.getElementById(\"SetLanguageStringslangToggled\").checked ?\n"
+              << "        document.getElementById(\"SetLanguageStringslang\").value : \"\";\n"
+              << "    embed.SetLanguageStrings(section, lang);\n"
+              << "    log(\"Language Strings set to '\" + section + \"' '\" + lang + \"'\");\n}\n\n"
+              << "function setUsbFilter()\n{\n"
+              << "    UsbFilterToggled = document.getElementById(\"SetUsbFilterfilterToggled\");\n"
+              << "    if (!UsbFilterToggled)\n        return;\n"
+              << "    filter = UsbFilterToggled.checked ?\n"
+              << "        document.getElementById(\"SetUsbFilterfilter\").value : \"\";\n"
+              << "    embed.SetUsbFilter(filter);\n"
+              << "    log(\"USB Filter String set to: '\" + filter + \"'\");\n}\n\n"
+              << "function show()\n{\n"
+              << "    embed.show();\n"
+              << "    log(\"Show\");\n}\n\n"
+              << "function ConnectedStatus()\n{\n"
+              << "    log(\"Connected status = \" + embed.ConnectedStatus());\n}\n\n"
+              << "function toggle(checkboxID)\n{\n"
+              << "    var checkbox = document.getElementById(checkboxID);\n"
+              << "    var toggle = document.getElementById(arguments[1]);\n"
+              << "    toggle.disabled = !checkbox.checked;\n}\n\n";
+}
+
+void Generator::generateFooter()
+{
+    std::cout << "<hr/>\n<table style=\"border: 1px; border-color: black;\">\n"
+              << "<caption>log:</caption>\n"
+              << "<thead><tr><th style=\"width: 22em;\">timestamp</th>"
+              << "<th>message</th></tr></thead>\n"
+              << "<tbody style=\"font-family: monospace;\" id=\"log\">\n"
+              << "</tbody>\n"
+              << "</table>\n"
+              << "</body>\n"
+              << "</html>\n";
+}
+
+void Generator::generateConnectVars()
+{
+    std::cout << "function setConnectVars()\n{\n";
+    std::list<Attribute>::iterator it;
+    for (it = m_attributes.begin(); it != m_attributes.end(); ++it) {
+        std::cout << "    embed." << it->getIdentifier() << " = "
+                  << "document.getElementById(\""
+                  << it->getIdentifier() << "Toggled\").checked ? "
+                  << "document.getElementById(\""
+                  << it->getIdentifier() << "\")."
+                  << (it->getType() == Token::T_BOOLEAN ? "checked" : "value")
+                  << " : \"\";\n";
+    }
+    std::cout << "\n}\n\n</script>\n\n";
+}
+
+void Generator::generateContent()
+{
+    std::cout << "<center>\n\n"
+              << "<table id=\"values\">\n";
+
+    std::list<Attribute>::iterator ita;
+    for (ita = m_attributes.begin(); ita != m_attributes.end(); ++ita) {
+        std::cout << "<tr>\n<td><input type=\"checkbox\" id=\""
+                  << ita->getIdentifier() << "Toggled"
+                  << "\" onclick=\"toggle('"
+                  << ita->getIdentifier() << "Toggled"
+                  <<"', '" << ita->getIdentifier()
+                  << "')\" " << (attributeEnabled(*ita) ? "checked" : "")
+                  << "/></td>\n"
+                  << "<td>" << splitIdentifier(ita->getIdentifier()) << "</td>\n"
+                  << "<td>" << attributeToHtmlElement(*ita)
+                  << "</td>\n</tr>\n";
+    }
+
+    std::list<Method>::iterator itm;
+    for (itm = m_methods.begin(); itm != m_methods.end(); ++itm) {
+        std::list<Method::MethodParam>::iterator itp;
+        std::list<Method::MethodParam> params = itm->getParams();
+        for (itp = params.begin(); itp != params.end(); ++itp) {
+            std::cout << "<tr>\n<td><input type=\"checkbox\" id=\""
+                      << itm->getIdentifier() << itp->getIdentifier()
+                      << "Toggled\" onclick=\"toggle('"
+                      << itm->getIdentifier() << itp->getIdentifier()
+                      << "Toggled', '" << itm->getIdentifier()
+                      << itp->getIdentifier() << "')\""
+                      << (methodEnabled(*itm, *itp) ? "checked" : "")
+                      << "/></td>\n<td>" << splitIdentifier(itm->getIdentifier())
+                      << " - " << splitIdentifier(itp->getIdentifier()) << "</td>\n"
+                      << "<td><input id=\"" << itm->getIdentifier() << itp->getIdentifier()
+                      << "\" type=\"" << (itp->getType() == Token::T_BOOLEAN ? "checkbox" : "text")
+                      << "\" size=\"30\" " << (methodEnabled(*itm, *itp) ? "" : "disabled ")
+                      << "/></td>\n</tr>\n";
+        }
+    }
+
+    std::cout << "</table>\n\n<br/>\n";
+
+    int i = 1;
+    for (itm = m_methods.begin(); itm != m_methods.end(); ++itm, ++i) {
+        std::cout << "<input type=\"button\" value=\""
+                  << splitIdentifier(itm->getIdentifier())
+                  << "\" style=\"min-width: 180px\" onclick=\""
+                  << itm->getIdentifier()
+                  << "()\"/>\n";
+        if (i % 3 == 0 && i != static_cast<int>(m_methods.size()))
+            std::cout << "<br/>\n";
+    }
+
+    std::cout << "\n</center>\n\n";
+}
+
+std::string Generator::lowerString(const std::string &str)
+{
+    std::string s(str);
+    std::transform(s.begin(), s.end(), s.begin(), tolower);
+    return s;
+}
+
+std::string Generator::splitIdentifier(const std::string &str)
+{
+    std::string result(str);
+    result[0] = toupper(result[0]);
+    for (size_t i = 1; i < result.size() - 1; ++i) {
+        if (isupper(result[i]) && islower(result[i + 1]))
+            result.insert(i++, " ");
+        else if (isupper(result[i]) && islower(result[i - 1]))
+            result.insert(i++, " ");
+    }
+    return result;
+}
+
+std::string Generator::attributeToHtmlElement(const Attribute &attr)
+{
+    std::stringstream ss;
+    std::string id = lowerString(attr.getIdentifier());
+    if (id == "truststore") {
+        ss << "<textarea id=\"" << attr.getIdentifier()
+           << "\" cols=\"66\" rows=\"33\" "
+           << (attributeEnabled(attr) ? "" : "disabled")
+           << "/></textarea>";
+    } else {
+        ss << "<input id=\"" << attr.getIdentifier() << "\" type=\""
+           << (attr.getType() == Token::T_BOOLEAN ? "checkbox" : "text")
+           <<"\" size=\"30\" " << attributeDefaultValue(attr)
+          << (attributeEnabled(attr) ? "" : "disabled ") << "/>";
+    }
+    return ss.str();
+}
+
+std::string Generator::attributeDefaultValue(const Attribute &attr)
+{
+    std::string id(lowerString(attr.getIdentifier()));
+    std::map<std::string, std::string>::iterator found = s_default_attribute_values.find(id);
+    return found != s_default_attribute_values.end() ? found->second : "";
+}
+
+bool Generator::attributeEnabled(const Attribute &attr)
+{
+    std::string id(lowerString(attr.getIdentifier()));
+    std::set<std::string>::iterator found = s_default_attributes.find(id);
+    return found != s_default_attributes.end();
+}
+
+bool Generator::methodEnabled(const Method &method, const Method::MethodParam &param)
+{
+    std::string id(lowerString(method.getIdentifier() + param.getIdentifier()));
+    std::set<std::string>::iterator found = s_default_methods.find(id);
+    return found != s_default_methods.end();
+}
diff --git a/generator.h b/generator.h
new file mode 100644
index 0000000..0a9bd3f
--- /dev/null
+++ b/generator.h
@@ -0,0 +1,59 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#ifndef GENERATOR_H
+#define GENERATOR_H
+
+#include <list>
+#include <map>
+#include <set>
+#include "attribute.h"
+#include "method.h"
+#include "token.h"
+
+class Generator
+{
+public:
+    Generator(const std::list<Attribute> &attributes,
+              const std::list<Method> &methods);
+    ~Generator();
+
+    void generate();
+
+private:
+    void init();
+    void generateHeader();
+    void generateFooter();
+    void generateConnectVars();
+    void generateContent();
+
+    static std::string lowerString(const std::string &str);
+    static std::string splitIdentifier(const std::string &str);
+    static std::string attributeDefaultValue(const Attribute &attr);
+    static std::string attributeToHtmlElement(const Attribute &attr);
+    static bool attributeEnabled(const Attribute &attr);
+    static bool methodEnabled(const Method &method, const Method::MethodParam &param);
+
+private:
+    std::list<Attribute> m_attributes;
+    std::list<Method> m_methods;
+    static std::set<std::string> s_default_attributes;
+    static std::set<std::string> s_default_methods;
+    static std::map<std::string, std::string> s_default_attribute_values;
+};
+
+#endif // GENERATOR_H
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..61f8db2
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,47 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#include "generator.h"
+#include "options.h"
+#include "parser.h"
+#include "redirecthelper.h"
+
+int main(int argc, char **argv)
+{
+    Options o(argc, argv);
+    if (!o.good())
+        return 1;
+
+    if (o.help()) {
+        o.printHelp();
+        return 0;
+    }
+
+    RedirectHelper rh(o);
+    if (!rh.redirect())
+        return 1;
+
+    Parser p;
+    if (!p.parse())
+        return 1;
+
+    Generator g(p.getAttributes(), p.getMethods());
+    g.generate();
+
+    rh.restore();
+    return 0;
+}
diff --git a/makefile b/makefile
new file mode 100644
index 0000000..2f4d516
--- /dev/null
+++ b/makefile
@@ -0,0 +1,24 @@
+CXX      = g++
+CXXFLAGS = -std=c++98 -g -pedantic -Wall
+
+SRCS = $(wildcard *.cpp)
+OBJS = $(SRCS:.cpp=.o)
+DEPS = $(SRCS:.cpp=.d)
+BIN  = generator
+OUT  = $(BIN) $(OBJS)
+
+$(BIN): $(OBJS)
+	$(CXX) $(CXXFLAGS) -o $@ $^
+
+%.o: %.cpp
+	$(CXX) $(CXXFLAGS) -c $<
+
+%.d: %.cpp
+	$(CXX) $(CXXFLAGS) -MM $< > $@
+
+clean:
+	rm -f $(OBJS) $(DEPS) $(BIN)
+
+ifeq ($(strip $(findstring $(MAKECMDGOALS), clean pack)),)
+-include $(DEPS)
+endif
diff --git a/method.h b/method.h
new file mode 100644
index 0000000..4b63461
--- /dev/null
+++ b/method.h
@@ -0,0 +1,100 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#ifndef METHOD_H
+#define METHOD_H
+
+#include <string>
+#include <list>
+#include "token.h"
+
+class Method
+{
+public:
+    class MethodParam;
+
+public:
+    Method(Token::TokenType type, std::string &identifier, const std::list<MethodParam> &params):
+        m_type(type),
+        m_identifier(identifier),
+        m_params(params)
+    {}
+
+    Method(const Method &copy):
+        m_type(copy.m_type),
+        m_identifier(copy.m_identifier),
+        m_params(copy.m_params)
+    {}
+
+    ~Method()
+    {}
+
+    Token::TokenType getType() const { return m_type; }
+    std::string getIdentifier() const { return m_identifier; }
+    std::list<MethodParam> getParams() const { return m_params; }
+
+    Method &operator=(const Method &rhs)
+    {
+        m_type = rhs.m_type;
+        m_identifier = rhs.m_identifier;
+        m_params = rhs.m_params;
+        return *this;
+    }
+
+private:
+    Token::TokenType m_type;
+    std::string m_identifier;
+    std::list<MethodParam> m_params;
+};
+
+class Method::MethodParam
+{
+public:
+    MethodParam(Token::TokenType dir, Token::TokenType type, const std::string &identifier):
+        m_dir(dir),
+        m_type(type),
+        m_identifier(identifier)
+    {}
+
+    MethodParam(const MethodParam &copy):
+        m_dir(copy.m_dir),
+        m_type(copy.m_type),
+        m_identifier(copy.m_identifier)
+    {}
+
+    ~MethodParam()
+    {}
+
+    Token::TokenType getType() const { return m_type; }
+    Token::TokenType getDir() const { return m_dir; }
+    std::string getIdentifier() const { return m_identifier; }
+
+    MethodParam &operator=(const MethodParam &rhs)
+    {
+        m_dir = rhs.m_dir;
+        m_type = rhs.m_type;
+        m_identifier = rhs.m_identifier;
+        return *this;
+    }
+
+private:
+    Token::TokenType m_dir;
+    Token::TokenType m_type;
+    std::string m_identifier;
+};
+
+#endif // METHOD_H
diff --git a/options.cpp b/options.cpp
new file mode 100644
index 0000000..86ea867
--- /dev/null
+++ b/options.cpp
@@ -0,0 +1,68 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#include <iostream>
+extern "C" {
+#  include <getopt.h>
+}
+#include "options.h"
+
+Options::Options(int argc, char **argv):
+    m_help(false),
+    m_good(true),
+    m_input_filename(),
+    m_output_filename()
+{
+    static struct option longopts[] = {
+        { "input",  required_argument, NULL, 'i' },
+        { "output", required_argument, NULL, 'o' },
+        { "help",   required_argument, NULL, 'h' },
+        { NULL,     0,                 NULL,  0  }
+    };
+
+    int c;
+    while ((c = getopt_long(argc, argv, "i:o:h", longopts, NULL)) != -1) {
+        switch (c) {
+        case 'i':
+            m_input_filename = optarg;
+            break;
+        case 'o':
+            m_output_filename = optarg;
+            break;
+        case 'h':
+            m_help = true;
+            break;
+        default:
+            m_good = false;
+            break;
+        }
+    }
+}
+
+Options::~Options()
+{
+}
+
+void Options::printHelp() const
+{
+    std::cout << "Spice-xpi test page generator\n\n"
+              << "Usage: ./generator [-h] [-i input] [-o output]\n\n"
+              << "Application options:\n"
+              << "  -i, --input     input filename (stdin used, if not specified)\n"
+              << "  -o, --output    output filename (stdout used, if not specified)\n"
+              << "  -h, --help      prints this help\n";
+}
diff --git a/options.h b/options.h
new file mode 100644
index 0000000..7c990e5
--- /dev/null
+++ b/options.h
@@ -0,0 +1,42 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+#include <string>
+
+class Options
+{
+public:
+    Options(int argc, char **argv);
+    ~Options();
+
+    bool help() const { return m_help; }
+    bool good() const { return m_good; }
+    void printHelp() const;
+    std::string inputFilename() const { return m_input_filename; }
+    std::string outputFilename() const { return m_output_filename; }
+
+private:
+    bool m_help;
+    bool m_good;
+    std::string m_input_filename;
+    std::string m_output_filename;
+};
+
+#endif // OPTIONS_H
diff --git a/parser.cpp b/parser.cpp
new file mode 100644
index 0000000..637a2cb
--- /dev/null
+++ b/parser.cpp
@@ -0,0 +1,430 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#include <iostream>
+#include <list>
+#include "parser.h"
+
+Parser::Parser():
+    m_scanner(),
+    m_token(),
+    m_attributes(),
+    m_methods()
+{
+}
+
+Parser::~Parser()
+{
+}
+
+bool Parser::parse()
+{
+    m_token = m_scanner.getNextToken();
+    while (1) {
+        if (m_token == Token::T_INTERFACE || m_token == Token::T_OPEN_BRACKET) {
+            if (!parseDefinition())
+                return false;
+        } else if (m_token == Token::T_HASH) {
+            if (!parseInclude())
+                return false;
+        } else if (m_token == Token::T_EOF) {
+            return true;
+        } else {
+            handleError();
+            return false;
+        }
+    }
+}
+
+void Parser::handleError() const
+{
+    std::cerr << (m_token == Token::T_LEX_ERROR ?
+        "Lexical error near line: " : "Syntax error near line: ");
+    std::cerr << m_scanner.getLineNo() << std::endl;
+}
+
+bool Parser::parseInclude()
+{
+    if (m_token != Token::T_HASH) {
+        handleError();
+        return false;
+    }
+
+    m_token = m_scanner.getNextToken();
+    if (m_token != Token::T_INCLUDE) {
+        handleError();
+        return false;
+    }
+
+    m_token = m_scanner.getNextToken();
+    if (m_token == Token::T_QUOTE) {
+        m_token = m_scanner.getNextToken();
+        while (m_token == Token::T_IDENTIFIER || m_token == Token::T_DOT)
+            m_token = m_scanner.getNextToken();
+        if (m_token != Token::T_QUOTE) {
+            handleError();
+            return false;
+        }
+        m_token = m_scanner.getNextToken();
+    } else if (m_token == Token::T_LESS) {
+        m_token = m_scanner.getNextToken();
+        while (m_token == Token::T_IDENTIFIER || m_token == Token::T_DOT)
+            m_token = m_scanner.getNextToken();
+        if (m_token != Token::T_GREATER) {
+            handleError();
+            return false;
+        }
+        m_token = m_scanner.getNextToken();
+    } else {
+        handleError();
+        return false;
+    }
+
+    return true;
+}
+
+bool Parser::parseDefinitionParams()
+{
+    if (m_token != Token::T_OPEN_BRACKET) {
+        handleError();
+        return false;
+    }
+
+    m_token = m_scanner.getNextToken();
+    while (1) {
+        if (m_token == Token::T_UUID) {
+            m_token = m_scanner.getNextToken();
+            if (m_token != Token::T_OPEN_PARENTHESES) {
+                handleError();
+                return false;
+            }
+            m_scanner.setAcceptUuids();
+            m_token = m_scanner.getNextToken();
+            if (m_token != Token::T_UUIDVAL) {
+                handleError();
+                return false;
+            }
+            m_scanner.setAcceptUuids(false);
+            m_token = m_scanner.getNextToken();
+            if (m_token != Token::T_CLOSE_PARENTHESES) {
+                handleError();
+                return false;
+            }
+        } else if (m_token != Token::T_IDENTIFIER) {
+            handleError();
+            return false;
+        }
+
+        m_token = m_scanner.getNextToken();
+        if (m_token == Token::T_CLOSE_BRACKET) {
+            m_token = m_scanner.getNextToken();
+            return true;
+        }
+        else if (m_token != Token::T_COMMA) {
+            handleError();
+            return false;
+        }
+
+        m_token = m_scanner.getNextToken();
+    }
+}
+
+bool Parser::parseDefinition()
+{
+    if (m_token == Token::T_OPEN_BRACKET && !parseDefinitionParams())
+        return false;
+
+    if (m_token != Token::T_INTERFACE) {
+        handleError();
+        return false;
+    }
+
+    m_token = m_scanner.getNextToken();
+    if (m_token != Token::T_IDENTIFIER) {
+        handleError();
+        return false;
+    }
+
+    m_token = m_scanner.getNextToken();
+    if (m_token == Token::T_COLON) {
+        m_token = m_scanner.getNextToken();
+        if (!parseBasicInterfaces())
+            return false;
+    }
+
+    if (m_token != Token::T_OPEN_BRACE) {
+        handleError();
+        return false;
+    }
+
+    m_token = m_scanner.getNextToken();
+    if (!parseInterfaceBody())
+        return false;
+
+    if (m_token != Token::T_CLOSE_BRACE) {
+        handleError();
+        return false;
+    }
+
+    m_token = m_scanner.getNextToken();
+    if (m_token != Token::T_SEMICOLON) {
+        handleError();
+        return false;
+    }
+
+    m_token = m_scanner.getNextToken();
+    return true;
+}
+
+bool Parser::parseBasicInterfaces()
+{
+    while (1) {
+        if (m_token != Token::T_IDENTIFIER) {
+            handleError();
+            return false;
+        }
+
+        m_token = m_scanner.getNextToken();
+        if (m_token == Token::T_OPEN_BRACE)
+            return true;
+
+        if (m_token != Token::T_COMMA) {
+            handleError();
+            return false;
+        }
+
+        m_token = m_scanner.getNextToken();
+    }
+}
+
+bool Parser::parseInterfaceBody()
+{
+    while (1) {
+        switch (m_token.getType()) {
+        case Token::T_READONLY:
+        case Token::T_ATTRIBUTE:
+            if (!parseAttribute())
+                return false;
+            break;
+        case Token::T_FLOAT:
+        case Token::T_DOUBLE:
+        case Token::T_STRING:
+        case Token::T_WSTRING:
+        case Token::T_UNSIGNED:
+        case Token::T_SHORT:
+        case Token::T_LONG:
+        case Token::T_CHAR:
+        case Token::T_WCHAR:
+        case Token::T_BOOLEAN:
+        case Token::T_VOID:
+        case Token::T_OCTET:
+            if (!parseMethod())
+                return false;
+            break;
+        case Token::T_CLOSE_BRACE:
+            break;
+        default:
+            handleError();
+            return false;
+        }
+
+        if (m_token == Token::T_LEX_ERROR) {
+            handleError();
+            return false;
+        }
+
+        if (m_token == Token::T_CLOSE_BRACE)
+            return true;
+    }
+    return true;
+}
+
+bool Parser::parseAttribute()
+{
+    bool readonly = false;
+    if (m_token == Token::T_READONLY) {
+        readonly = true;
+        m_token = m_scanner.getNextToken();
+    }
+
+    if (m_token != Token::T_ATTRIBUTE) {
+        handleError();
+        return false;
+    }
+
+    m_token = m_scanner.getNextToken();
+    if (!parseType())
+        return false;
+
+    Token type = m_token;
+
+    m_token = m_scanner.getNextToken();
+    if (m_token != Token::T_IDENTIFIER) {
+        handleError();
+        return false;
+    }
+
+    m_attributes.push_back(Attribute(type.getType(), m_token.getParameter(), readonly));
+
+    m_token = m_scanner.getNextToken();
+    if (m_token == Token::T_COMMA) {
+        while (1) {
+            m_token = m_scanner.getNextToken();
+            if (m_token != Token::T_IDENTIFIER) {
+                handleError();
+                return false;
+            }
+
+            m_attributes.push_back(Attribute(type.getType(), m_token.getParameter(), readonly));
+            m_token = m_scanner.getNextToken();
+            if (m_token == Token::T_SEMICOLON) {
+                break;
+            } else if (m_token != Token::T_COMMA) {
+                handleError();
+                return false;
+            }
+        }
+    }
+    if (m_token != Token::T_SEMICOLON) {
+        handleError();
+        return false;
+    }
+
+    m_token = m_scanner.getNextToken();
+    return true;
+}
+
+bool Parser::parseMethod()
+{
+    if (!parseType())
+        return false;
+
+    Token type = m_token;
+    m_token = m_scanner.getNextToken();
+    if (m_token != Token::T_IDENTIFIER) {
+        handleError();
+        return false;
+    }
+
+    std::string identifier = m_token.getParameter();
+
+    m_token = m_scanner.getNextToken();
+    if (m_token != Token::T_OPEN_PARENTHESES) {
+        handleError();
+        return false;
+    }
+
+    std::list<Method::MethodParam> params;
+    m_token = m_scanner.getNextToken();
+    if (m_token != Token::T_CLOSE_PARENTHESES) {
+        while (1) {
+            Token dir(Token::T_UNKNOWN);
+            if (m_token == Token::T_IN ||
+                m_token == Token::T_OUT ||
+                m_token == Token::T_INOUT)
+            {
+                dir = m_token;
+                m_token = m_scanner.getNextToken();
+            }
+            if (!parseType())
+                return false;
+            Token type = m_token;
+            m_token = m_scanner.getNextToken();
+            if (m_token != Token::T_IDENTIFIER) {
+                handleError();
+                return false;
+            }
+
+            params.push_back(Method::MethodParam(dir.getType(), type.getType(),
+                m_token.getParameter()));
+
+            m_token = m_scanner.getNextToken();
+            if (m_token == Token::T_COMMA) {
+                m_token = m_scanner.getNextToken();
+            } else if (m_token == Token::T_CLOSE_PARENTHESES) {
+                break;
+            } else {
+                handleError();
+                return false;
+            }
+        }
+    }
+
+    if (m_token != Token::T_CLOSE_PARENTHESES) {
+        handleError();
+        return false;
+    }
+
+    m_token = m_scanner.getNextToken();
+    if (m_token != Token::T_SEMICOLON) {
+        handleError();
+        return false;
+    }
+
+    m_methods.push_back(Method(type.getType(), identifier, params));
+
+    m_token = m_scanner.getNextToken();
+    return true;
+}
+
+bool Parser::parseType()
+{
+    switch (m_token.getType()) {
+    case Token::T_UNSIGNED: {
+        Token new_token = m_scanner.getNextToken();
+        if (new_token == Token::T_SHORT)
+            m_token = Token(Token::T_UNSIGNED_SHORT);
+        else if (new_token == Token::T_LONG) {
+            Token newest_token = m_scanner.getNextToken();
+            if (newest_token == Token::T_LONG) {
+                m_token = Token(Token::T_UNSIGNED_LONG_LONG);
+            } else {
+                m_scanner.pushToken(newest_token);
+                m_token = Token(Token::T_UNSIGNED_LONG);
+            }
+        } else {
+            m_scanner.pushToken(new_token);
+            handleError();
+            return false;
+        }
+        break;
+    }
+    case Token::T_LONG: {
+        Token new_token = m_scanner.getNextToken();
+        if (new_token == Token::T_LONG)
+            m_token = Token(Token::T_LONG_LONG);
+        else
+            m_scanner.pushToken(new_token);
+        break;
+    }
+    case Token::T_FLOAT:
+    case Token::T_DOUBLE:
+    case Token::T_STRING:
+    case Token::T_WSTRING:
+    case Token::T_SHORT:
+    case Token::T_CHAR:
+    case Token::T_WCHAR:
+    case Token::T_BOOLEAN:
+    case Token::T_VOID:
+    case Token::T_OCTET:
+        return true;
+    default:
+        handleError();
+        return false;
+    }
+    return true;
+}
diff --git a/parser.h b/parser.h
new file mode 100644
index 0000000..18ebed1
--- /dev/null
+++ b/parser.h
@@ -0,0 +1,56 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#ifndef PARSER_H
+#define PARSER_H
+
+#include <list>
+#include "attribute.h"
+#include "method.h"
+#include "scanner.h"
+#include "token.h"
+
+class Parser
+{
+public:
+    Parser();
+    ~Parser();
+
+    bool parse();
+
+    std::list<Attribute> getAttributes() const { return m_attributes; }
+    std::list<Method> getMethods() const { return m_methods; }
+
+private:
+    void handleError() const;
+    bool parseInclude();
+    bool parseDefinitionParams();
+    bool parseDefinition();
+    bool parseBasicInterfaces();
+    bool parseInterfaceBody();
+    bool parseAttribute();
+    bool parseType();
+    bool parseMethod();
+
+private:
+    Scanner m_scanner;
+    Token m_token;
+    std::list<Attribute> m_attributes;
+    std::list<Method> m_methods;
+};
+
+#endif // PARSER_H
diff --git a/redirecthelper.cpp b/redirecthelper.cpp
new file mode 100644
index 0000000..4455216
--- /dev/null
+++ b/redirecthelper.cpp
@@ -0,0 +1,77 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#include "redirecthelper.h"
+
+RedirectHelper::RedirectHelper(const Options &o):
+    m_input_file(o.inputFilename()),
+    m_output_file(o.outputFilename()),
+    m_ifb(),
+    m_ofb(),
+    m_cin_streambuf(NULL),
+    m_cout_streambuf(NULL)
+{
+}
+
+RedirectHelper::~RedirectHelper()
+{
+    restore();
+}
+
+bool RedirectHelper::redirect()
+{
+    if (!m_cin_streambuf && !m_input_file.empty()) {
+        m_ifb.open(m_input_file.c_str(), std::ios::in);
+        if (!m_ifb.is_open()) {
+            std::cerr << "Unable to open '" << m_input_file << "' for reading!\n";
+            return false;
+        } else {
+            m_cin_streambuf = std::cin.rdbuf();
+            std::istream is(&m_ifb);
+            std::cin.rdbuf(is.rdbuf());
+        }
+    }
+
+    if (!m_cout_streambuf && !m_output_file.empty()) {
+        m_ofb.open(m_output_file.c_str(), std::ios::out);
+        if (!m_ofb.is_open()) {
+            std::cerr << "Unable to open '" << m_output_file << "' for writing!\n";
+            return false;
+        } else {
+            m_cout_streambuf = std::cout.rdbuf();
+            std::ostream os(&m_ofb);
+            std::cout.rdbuf(os.rdbuf());
+        }
+    }
+
+    return true;
+}
+
+void RedirectHelper::restore()
+{
+    if (m_cin_streambuf) {
+        m_ifb.close();
+        std::cin.rdbuf(m_cin_streambuf);
+        m_cin_streambuf = NULL;
+    }
+
+    if (m_cout_streambuf) {
+        m_ofb.close();
+        std::cout.rdbuf(m_cout_streambuf);
+        m_cout_streambuf = NULL;
+    }
+}
diff --git a/redirecthelper.h b/redirecthelper.h
new file mode 100644
index 0000000..786c91e
--- /dev/null
+++ b/redirecthelper.h
@@ -0,0 +1,44 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#ifndef REDIRECTHELPER_H
+#define REDIRECTHELPER_H
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include "options.h"
+
+class RedirectHelper
+{
+public:
+    RedirectHelper(const Options &o);
+    ~RedirectHelper();
+
+    bool redirect();
+    void restore();
+
+private:
+    std::string m_input_file;
+    std::string m_output_file;
+    std::filebuf m_ifb;
+    std::filebuf m_ofb;
+    std::streambuf *m_cin_streambuf;
+    std::streambuf *m_cout_streambuf;
+};
+
+#endif // REDIRECTHELPER_H
diff --git a/scanner.cpp b/scanner.cpp
new file mode 100644
index 0000000..f475e73
--- /dev/null
+++ b/scanner.cpp
@@ -0,0 +1,233 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#include <fstream>
+#include <cctype>
+#include "scanner.h"
+
+std::map<std::string, Token::TokenType> Scanner::s_keywords;
+
+Scanner::Scanner():
+    m_token(),
+    m_line_no_start(1),
+    m_line_no_end(m_line_no_start),
+    m_accept_uuids(false),
+    m_token_queue()
+{
+    if (s_keywords.empty())
+        initKeywords();
+}
+
+Scanner::~Scanner()
+{
+}
+
+void Scanner::initKeywords()
+{
+    s_keywords["attribute"] = Token::T_ATTRIBUTE;
+    s_keywords["const"] = Token::T_CONST;
+    s_keywords["in"] = Token::T_IN;
+    s_keywords["out"] = Token::T_OUT;
+    s_keywords["inout"] = Token::T_INOUT;
+    s_keywords["interface"] = Token::T_INTERFACE;
+    s_keywords["native"] = Token::T_NATIVE;
+    s_keywords["raises"] = Token::T_RAISES;
+    s_keywords["readonly"] = Token::T_READONLY;
+    s_keywords["typedef"] = Token::T_TYPEDEF;
+    s_keywords["uuid"] = Token::T_UUID;
+    s_keywords["float"] = Token::T_FLOAT;
+    s_keywords["double"] = Token::T_DOUBLE;
+    s_keywords["string"] = Token::T_STRING;
+    s_keywords["wstring"] = Token::T_WSTRING;
+    s_keywords["unsigned"] = Token::T_UNSIGNED;
+    s_keywords["short"] = Token::T_SHORT;
+    s_keywords["long"] = Token::T_LONG;
+    s_keywords["char"] = Token::T_CHAR;
+    s_keywords["wchar"] = Token::T_WCHAR;
+    s_keywords["boolean"] = Token::T_BOOLEAN;
+    s_keywords["void"] = Token::T_VOID;
+    s_keywords["octet"] = Token::T_OCTET;
+    s_keywords["include"] = Token::T_INCLUDE;
+}
+
+Token Scanner::getNextToken()
+{
+    if (!m_token_queue.empty()) {
+        Token t(m_token_queue.front());
+        m_token_queue.pop();
+        return t;
+    }
+
+    enum { S_INITIAL, S_IDENTIFIER,
+           S_NUMBER, S_COLON,
+           S_SHIFT_LEFT, S_SHIFT_RIGHT,
+           S_SLASH, S_BLOCK_COMMENT,
+           S_UUID } state = S_INITIAL;
+    std::string param;
+    int c = 0;
+    int old_c;
+
+    m_line_no_start = m_line_no_end;
+    while (1) {
+        old_c = c;
+        c = std::cin.get();
+
+        if (std::cin.eof())
+            return Token(state == S_INITIAL ? Token::T_EOF : Token::T_LEX_ERROR);
+
+        if (c == '\n')
+            ++m_line_no_end;
+
+        switch (state) {
+        case S_INITIAL:
+            if (isalpha(c)) {
+                param += c;
+                state = m_accept_uuids ? S_UUID : S_IDENTIFIER;
+            } else if (isdigit(c)) {
+                param += c;
+                state = m_accept_uuids ? S_UUID : S_NUMBER;
+            }
+            switch (c) {
+            case '[':
+                return Token(Token::T_OPEN_BRACKET);
+            case ']':
+                return Token(Token::T_CLOSE_BRACKET);
+            case '{':
+                return Token(Token::T_OPEN_BRACE);
+            case '}':
+                return Token(Token::T_CLOSE_BRACE);
+            case '(':
+                return Token(Token::T_OPEN_PARENTHESES);
+            case ')':
+                return Token(Token::T_CLOSE_PARENTHESES);
+            case ',':
+                return Token(Token::T_COMMA);
+            case '=':
+                return Token(Token::T_ASSIGN);
+            case ';':
+                return Token(Token::T_SEMICOLON);
+            case '|':
+                return Token(Token::T_PIPE);
+            case '^':
+                return Token(Token::T_CARET);
+            case '&':
+                return Token(Token::T_AMPERSAND);
+            case '+':
+                return Token(Token::T_PLUS);
+            case '-':
+                return Token(Token::T_MINUS);
+            case '%':
+                return Token(Token::T_PERCENT);
+            case '~':
+                return Token(Token::T_TILDE);
+            case '#':
+                return Token(Token::T_HASH);
+            case '"':
+                return Token(Token::T_QUOTE);
+            case '.':
+                return Token(Token::T_DOT);
+            case ':':
+                state = S_COLON;
+                break;
+            case '<':
+                state = S_SHIFT_LEFT;
+                break;
+            case '>':
+                state = S_SHIFT_RIGHT;
+                break;
+            case '/':
+                state = S_SLASH;
+                break;
+            case ' ':
+            case '\t':
+                break;
+            case '*':
+                return Token(Token::T_LEX_ERROR);
+            }
+            break;
+
+        case S_IDENTIFIER:
+            if (!isalnum(c) && c != '-' && c != '_') {
+                std::cin.unget();
+                std::map<std::string, Token::TokenType>::iterator it;
+                it = s_keywords.find(param);
+                if (it != s_keywords.end())
+                    return Token(it->second);
+                return Token(Token::T_IDENTIFIER, param);
+            }
+            param += c;
+            break;
+
+        case S_NUMBER:
+            if (!isdigit(c)) {
+                std::cin.unget();
+                return Token(Token::T_NUMBER, param);
+            }
+            param += c;
+            break;
+
+        case S_UUID: {
+            if (!isdigit(c) && c != '-' && (c > 'f' || c < 'a')) {
+                std::cin.unget();
+                return Token(param.size() != 36 ? Token::T_LEX_ERROR : Token::T_UUIDVAL, param);
+            }
+            const int len = param.size();
+            if (c != '-' && len >= 36 &&
+               (len == 8 || len == 13 || len == 18 || len == 23))
+            {
+                return Token(Token::T_LEX_ERROR);
+            }
+            param += c;
+            break;
+        }
+
+        case S_COLON:
+            if (c == ':')
+                return Token(Token::T_DOUBLECOLON);
+            std::cin.unget();
+            return Token(Token::T_COLON);
+
+        case S_SHIFT_LEFT:
+            if (c == '<')
+                return Token(Token::T_SHIFT_LEFT);
+            std::cin.unget();
+            return Token(Token::T_LESS);
+
+        case S_SHIFT_RIGHT:
+            if (c == '>')
+                return Token(Token::T_SHIFT_RIGHT);
+            std::cin.unget();
+            return Token(Token::T_GREATER);
+
+        case S_SLASH:
+            if (c == '/') {
+                while (c != '\n' && !std::cin.eof())
+                    c = std::cin.get();
+                state = S_INITIAL;
+            } else if (c == '*') {
+                state = S_BLOCK_COMMENT;
+            }
+            break;
+
+        case S_BLOCK_COMMENT:
+            if (old_c == '*' && c == '/')
+                state = S_INITIAL;
+            break;
+        }
+    }
+    return Token(Token::T_LEX_ERROR);
+}
diff --git a/scanner.h b/scanner.h
new file mode 100644
index 0000000..d6150a2
--- /dev/null
+++ b/scanner.h
@@ -0,0 +1,50 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#ifndef SCANNER_H
+#define SCANNER_H
+
+#include <iostream>
+#include <string>
+#include <map>
+#include <queue>
+#include "token.h"
+
+class Scanner
+{
+public:
+    Scanner();
+    ~Scanner();
+
+    Token getNextToken();
+    int getLineNo() const { return m_line_no_start; }
+    void pushToken(Token &token) { m_token_queue.push(token); }
+    void setAcceptUuids(bool accept = true) { m_accept_uuids = accept; }
+
+private:
+    static void initKeywords();
+
+private:
+    Token m_token;
+    int m_line_no_start;
+    int m_line_no_end;
+    bool m_accept_uuids;
+    std::queue<Token> m_token_queue;
+    static std::map<std::string, Token::TokenType> s_keywords;
+};
+
+#endif // SCANNER_H
diff --git a/token.h b/token.h
new file mode 100644
index 0000000..3676449
--- /dev/null
+++ b/token.h
@@ -0,0 +1,77 @@
+/* ***** BEGIN LICENSE BLOCK *****
+*   Copyright (C) 2012, Peter Hatina <phatina at redhat.com>
+*
+*   This program is free software; you can redistribute it and/or
+*   modify it under the terms of the GNU General Public License as
+*   published by the Free Software Foundation; either version 2 of
+*   the License, or (at your option) any later version.
+*
+*   This program 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 General Public License for more details.
+*
+*   You should have received a copy of the GNU General Public License
+*   along with this program. If not, see <http://www.gnu.org/licenses/>.
+* ***** END LICENSE BLOCK ***** */
+
+#ifndef TOKEN_H
+#define TOKEN_H
+
+#include <string>
+
+class Token
+{
+public:
+    typedef enum {
+        T_UNKNOWN, T_ATTRIBUTE, T_CONST, T_IN, T_OUT, T_INOUT,
+        T_INTERFACE, T_NATIVE, T_RAISES, T_TYPEDEF, T_UUID, T_UUIDVAL,
+        T_OPEN_BRACKET, T_CLOSE_BRACKET, T_OPEN_BRACE, T_CLOSE_BRACE,
+        T_OPEN_PARENTHESES, T_CLOSE_PARENTHESES, T_COMMA, T_COLON,
+        T_ASSIGN, T_SEMICOLON, T_INCLUDE, T_READONLY, T_FLOAT, T_DOUBLE,
+        T_STRING, T_WSTRING, T_UNSIGNED, T_SHORT, T_LONG, T_CHAR, T_WCHAR,
+        T_BOOLEAN, T_VOID, T_OCTET, T_DOUBLECOLON, T_PIPE, T_CARET, T_AMPERSAND,
+        T_SHIFT_LEFT, T_SHIFT_RIGHT, T_PLUS, T_MINUS, T_ASTERISK, T_SLASH,
+        T_PERCENT, T_TILDE, T_IDENTIFIER, T_EOF, T_NUMBER, T_HASH,
+        T_LEX_ERROR, T_UNSIGNED_SHORT, T_UNSIGNED_LONG, T_UNSIGNED_LONG_LONG,
+        T_LONG_LONG, T_LESS, T_GREATER, T_QUOTE, T_DOT
+    } TokenType;
+
+public:
+    Token():
+        m_type(T_UNKNOWN),
+        m_param()
+    {}
+
+    Token(TokenType type, const std::string &parameter = std::string()):
+        m_type(type),
+        m_param(parameter)
+    {}
+
+    Token(const Token &copy):
+        m_type(copy.m_type),
+        m_param(copy.m_param)
+    {}
+
+    ~Token()
+    {}
+
+    TokenType getType() const { return m_type; }
+    std::string getParameter() const { return m_param; }
+
+    Token &operator= (const Token& rhs)
+    {
+        m_type = rhs.m_type;
+        m_param = rhs.m_param;
+        return *this;
+    }
+
+    bool operator==(Token::TokenType type) const { return m_type == type; }
+    bool operator!=(Token::TokenType type) const { return m_type != type; }
+
+private:
+    TokenType m_type;
+    std::string m_param;
+};
+
+#endif // TOKEN_H
-- 
1.7.10.2



More information about the Spice-devel mailing list