working on implementing generate_spirv_parser::parser::parse
authorJacob Lifshay <programmerjake@gmail.com>
Sat, 3 Jun 2017 02:37:19 +0000 (19:37 -0700)
committerJacob Lifshay <programmerjake@gmail.com>
Sat, 3 Jun 2017 02:37:19 +0000 (19:37 -0700)
src/generate_spirv_parser/CMakeLists.txt
src/generate_spirv_parser/ast.cpp [new file with mode: 0644]
src/generate_spirv_parser/ast.h [new file with mode: 0644]
src/generate_spirv_parser/generate_spirv_parser.cpp
src/generate_spirv_parser/parser.cpp [new file with mode: 0644]
src/generate_spirv_parser/parser.h [new file with mode: 0644]
src/json/json.h
src/spirv/CMakeLists.txt

index e2954a08fbe278d194423fdc877e319979ed20cd..f55fd72d82e537df079cf669a5aac7185e44eb44 100644 (file)
@@ -19,6 +19,8 @@
 # SOFTWARE.
 #
 cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
-set(sources generate_spirv_parser.cpp) 
+set(sources ast.cpp
+            generate_spirv_parser.cpp
+            parser.cpp) 
 add_executable(generate_spirv_parser ${sources})
 target_link_libraries(generate_spirv_parser util json)
diff --git a/src/generate_spirv_parser/ast.cpp b/src/generate_spirv_parser/ast.cpp
new file mode 100644 (file)
index 0000000..9e4b259
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+* Copyright 2017 Jacob Lifshay
+*
+* 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.
+*
+*/
+#include "ast.h"
diff --git a/src/generate_spirv_parser/ast.h b/src/generate_spirv_parser/ast.h
new file mode 100644 (file)
index 0000000..eedb5c7
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+* Copyright 2017 Jacob Lifshay
+*
+* 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.
+*
+*/
+
+#ifndef GENERATE_SPIRV_PARSER_AST_H_
+#define GENERATE_SPIRV_PARSER_AST_H_
+
+#include "../json/json.h"
+#include <cstdint>
+
+namespace vulkan_cpu
+{
+namespace generate_spirv_parser
+{
+namespace ast
+{
+struct copyright
+{
+    json::ast::array value;
+    copyright() : value()
+    {
+    }
+    explicit copyright(json::ast::array value) noexcept : value(std::move(value))
+    {
+    }
+};
+
+struct top_level
+{
+    copyright copyright;
+    std::uint32_t magic_number;
+    std::size_t major_version;
+    std::size_t minor_version;
+#warning finish adding members
+    top_level(ast::copyright copyright,
+              std::uint32_t magic_number,
+              std::size_t major_version,
+              std::size_t minor_version)
+        : copyright(std::move(copyright)),
+          magic_number(magic_number),
+          major_version(major_version),
+          minor_version(minor_version)
+    {
+    }
+};
+}
+}
+}
+
+#endif /* GENERATE_SPIRV_PARSER_AST_H_ */
index e38b2169d8b140ea395fb2be695f5b5f447adf2e..feaa3c844a2092da71b8121d7f3a42cc1cd36163 100644 (file)
 #include <iostream>
 #include "../json/json.h"
 #include "../json/parser.h"
+#include "parser.h"
+#include "../util/optional.h"
 
 namespace vulkan_cpu
 {
 namespace generate_spirv_parser
 {
-namespace ast = json::ast;
-
 int generate_spirv_parser_main(int argc, char **argv)
 {
     std::string file_name;
@@ -42,16 +42,22 @@ int generate_spirv_parser_main(int argc, char **argv)
     }
     try
     {
-        const auto source = file_name == "-" ? json::source::load_stdin() :
-                                               json::source::load_file(std::move(file_name));
-        auto value = json::parse(&source);
-        json::write(std::cout, value, json::write_options::pretty());
-        std::cout << std::endl;
-    }
-    catch(json::parse_error &e)
-    {
-        std::cerr << "error: " << e.what() << std::endl;
-        return 1;
+        auto source = file_name == "-" ? json::source::load_stdin() :
+                                         json::source::load_file(std::move(file_name));
+        try
+        {
+            auto ast = parser::parse(json::parse(&source));
+        }
+        catch(json::parse_error &e)
+        {
+            std::cerr << "error: " << e.what() << std::endl;
+            return 1;
+        }
+        catch(parser::parse_error &e)
+        {
+            std::cerr << "error: " << e.what() << std::endl;
+            return 1;
+        }
     }
     catch(std::exception &e)
     {
diff --git a/src/generate_spirv_parser/parser.cpp b/src/generate_spirv_parser/parser.cpp
new file mode 100644 (file)
index 0000000..ee7595f
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+* Copyright 2017 Jacob Lifshay
+*
+* 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.
+*
+*/
+#include "parser.h"
+#include "../util/optional.h"
+#include <sstream>
+#include <limits>
+
+namespace vulkan_cpu
+{
+namespace generate_spirv_parser
+{
+namespace parser
+{
+std::string path::to_string() const
+{
+    std::ostringstream ss;
+    ss << "root";
+    for(auto &e : elements)
+    {
+        ss << '[';
+        if(util::holds_alternative<std::size_t>(e))
+        {
+            ss << util::get<std::size_t>(e);
+        }
+        else
+        {
+            json::ast::string_value::write(ss, util::get<std::string>(e));
+        }
+        ss << ']';
+    }
+    return ss.str();
+}
+
+namespace
+{
+template <typename Value, std::size_t N>
+Value get_value_or_throw_parse_error(util::optional<Value> value,
+                                     path_builder_base *path_builder,
+                                     const char(&message)[N])
+{
+    if(value)
+        return std::move(*value);
+    throw parse_error(path_builder ? path_builder->path() : path{}, message);
+}
+
+ast::copyright parse_copyright(json::ast::value value, const path_builder_base *parent_path_builder)
+{
+    if(json::ast::get_value_kind(value) != json::ast::value_kind::array)
+        throw parse_error(parent_path_builder->path(), "copyright is not an array");
+    auto &copyright_array =
+        static_cast<json::ast::array &>(*util::get<json::ast::composite_value_pointer>(value));
+    for(std::size_t index = 0; index < copyright_array.values.size(); index++)
+    {
+        path_builder<std::size_t> path_builder(&index, parent_path_builder);
+        auto &element = copyright_array.values[index];
+        if(json::ast::get_value_kind(element) != json::ast::value_kind::string)
+            throw parse_error(parent_path_builder->path(),
+                              "copyright array's element is not a string");
+    }
+    return ast::copyright(std::move(copyright_array));
+}
+
+template <typename T>
+T parse_integer(const json::ast::value &value,
+                const path_builder_base *parent_path_builder,
+                const char *name)
+{
+    if(json::ast::get_value_kind(value) != json::ast::value_kind::number)
+        throw parse_error(parent_path_builder->path(), std::string(name) + " is not a number");
+    auto number_value = util::get<json::ast::number_value>(value);
+    T retval = number_value.value;
+    if(retval != number_value.value) // not an exact value
+        throw parse_error(parent_path_builder->path(), std::string(name) + " is not an integer");
+    return retval;
+}
+
+constexpr int get_digit_value(int ch, unsigned base) noexcept
+{
+    unsigned retval{};
+    if(ch >= '0' && ch <= '9')
+        retval = ch - '0';
+    else if(ch >= 'a' && ch <= 'z')
+        retval = ch - 'a' + 0xA;
+    else if(ch >= 'A' && ch <= 'Z')
+        retval = ch - 'A' + 0xA;
+    else
+        return -1;
+    if(retval >= base)
+        return -1;
+    return retval;
+}
+
+template <typename T>
+T parse_hex_integer_string(const json::ast::value &value,
+                           const path_builder_base *parent_path_builder,
+                           const char *name,
+                           std::size_t min_length,
+                           std::size_t max_length)
+{
+    if(json::ast::get_value_kind(value) != json::ast::value_kind::string)
+        throw parse_error(parent_path_builder->path(), std::string(name) + " is not a string");
+    auto &string_value = util::get<json::ast::string_value>(value);
+    constexpr std::size_t hex_number_prefix_length = 2; // std::strlen("0x")
+    if(string_value.value.size() < hex_number_prefix_length || string_value.value[0] != '0'
+       || (string_value.value[1] != 'x' && string_value.value[1] != 'X'))
+        throw parse_error(parent_path_builder->path(),
+                          std::string(name) + " is not a valid hex number in a string");
+    constexpr T max_value = std::numeric_limits<T>::max();
+    constexpr unsigned base = 0x10;
+    T retval = 0;
+    std::size_t digit_count = 0;
+    for(std::size_t i = hex_number_prefix_length; i < string_value.value.size(); i++)
+    {
+        digit_count++;
+        char ch = string_value.value[i];
+        int digit = get_digit_value(ch, base);
+        if(digit < 0)
+            throw parse_error(parent_path_builder->path(),
+                              std::string(name) + ": not a valid hex digit");
+        if(digit_count > max_length)
+            throw parse_error(parent_path_builder->path(),
+                              std::string(name) + " has too many digits");
+        if(retval > max_value / base
+           || (retval = max_value / base && static_cast<unsigned>(digit) > max_value % base))
+            throw parse_error(parent_path_builder->path(), std::string(name) + ": value too big");
+        retval *= base;
+        retval += digit;
+    }
+    if(digit_count < min_length)
+        throw parse_error(parent_path_builder->path(),
+                          std::string(name) + " doesn't have enough digits");
+    return retval;
+}
+}
+
+ast::top_level parse(json::ast::value &&top_level_value)
+{
+    if(json::ast::get_value_kind(top_level_value) != json::ast::value_kind::object)
+        throw parse_error({}, "top level value is not an object");
+    auto &top_level_object = static_cast<const json::ast::object &>(
+        *util::get<json::ast::composite_value_pointer>(top_level_value));
+    util::optional<ast::copyright> copyright;
+    util::optional<std::uint32_t> magic_number;
+    util::optional<std::size_t> major_version;
+    util::optional<std::size_t> minor_version;
+#warning finish adding ast::top_level members
+    for(auto &entry : top_level_object.values)
+    {
+        const auto &key = std::get<0>(entry);
+        auto &entry_value = std::get<1>(entry);
+        path_builder<std::string> path_builder(&key, nullptr);
+        if(key == "copyright")
+        {
+            copyright = parse_copyright(std::move(entry_value), &path_builder);
+        }
+        else if(key == "magic_number")
+        {
+            magic_number = parse_hex_integer_string<std::uint32_t>(
+                entry_value, &path_builder, "magic_number", 1, 8);
+        }
+        else if(key == "major_version")
+        {
+            major_version = parse_integer<std::size_t>(entry_value, &path_builder, "major_version");
+        }
+        else if(key == "minor_version")
+        {
+            minor_version = parse_integer<std::size_t>(entry_value, &path_builder, "minor_version");
+        }
+        else
+        {
+            throw parse_error(path_builder.path(), "unknown key");
+        }
+    }
+    return ast::top_level(
+        get_value_or_throw_parse_error(std::move(copyright), nullptr, "missing copyright"),
+        get_value_or_throw_parse_error(magic_number, nullptr, "missing magic_number"),
+        get_value_or_throw_parse_error(major_version, nullptr, "missing major_version"),
+        get_value_or_throw_parse_error(minor_version, nullptr, "missing minor_version"));
+}
+}
+}
+}
\ No newline at end of file
diff --git a/src/generate_spirv_parser/parser.h b/src/generate_spirv_parser/parser.h
new file mode 100644 (file)
index 0000000..67e86bb
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+* Copyright 2017 Jacob Lifshay
+*
+* 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.
+*
+*/
+
+#ifndef GENERATE_SPIRV_PARSER_PARSER_H_
+#define GENERATE_SPIRV_PARSER_PARSER_H_
+
+#include "ast.h"
+#include <stdexcept>
+#include <cassert>
+#include <string>
+#include <vector>
+#include "../util/variant.h"
+#include "../json/json.h"
+
+namespace vulkan_cpu
+{
+namespace generate_spirv_parser
+{
+namespace parser
+{
+struct path
+{
+    typedef util::variant<std::size_t, std::string> element;
+    std::vector<element> elements;
+    path() : elements()
+    {
+    }
+    path(std::vector<element> elements) : elements(std::move(elements))
+    {
+    }
+    path(std::initializer_list<element> elements) : elements(elements)
+    {
+    }
+    std::string to_string() const;
+};
+
+struct path_builder_base
+{
+    path_builder_base(const path_builder_base &) = delete;
+    path_builder_base &operator=(const path_builder_base &) = delete;
+    virtual ~path_builder_base() = default;
+    const path_builder_base *const parent;
+    const std::size_t element_count;
+    explicit path_builder_base(const path_builder_base *parent) noexcept
+        : parent(parent),
+          element_count(parent ? parent->element_count + 1 : 1)
+    {
+    }
+    virtual path::element get_element() const = 0;
+    path path() const
+    {
+        std::vector<path::element> elements;
+        elements.resize(element_count);
+        const path_builder_base *node = this;
+        for(std::size_t i = 0, j = element_count - 1; i < element_count;
+            i++, j--, node = node->parent)
+        {
+            assert(node);
+            elements[j] = node->get_element();
+        }
+        assert(!node);
+        return std::move(elements);
+    }
+};
+
+template <typename T>
+struct path_builder final : public path_builder_base
+{
+    const T *value;
+    path_builder(const T *value, const path_builder_base *parent) noexcept
+        : path_builder_base(parent),
+          value(value)
+    {
+    }
+    virtual path::element get_element() const override
+    {
+        return *value;
+    }
+};
+
+class parse_error : public std::runtime_error
+{
+public:
+    path path;
+    parse_error(parser::path path, const std::string &message)
+        : runtime_error("at " + path.to_string() + ": " + message), path(std::move(path))
+    {
+    }
+};
+
+ast::top_level parse(json::ast::value &&top_level_value);
+}
+}
+}
+
+#endif /* GENERATE_SPIRV_PARSER_PARSER_H_ */
index 2676a4e37468ea587f5f03aa945eb140e6171e85..8ac3070bd24992613c2dae8c4f820ae9291bc4b2 100644 (file)
@@ -103,6 +103,16 @@ struct write_state
 
 namespace ast
 {
+enum class value_kind
+{
+    null,
+    boolean,
+    string,
+    number,
+    object,
+    array
+};
+
 struct null_value final
 {
     constexpr null_value() noexcept = default;
@@ -122,6 +132,10 @@ struct null_value final
     {
         return *this;
     }
+    constexpr value_kind get_value_kind() const noexcept
+    {
+        return value_kind::null;
+    }
 };
 
 struct boolean_value final
@@ -144,6 +158,10 @@ struct boolean_value final
     {
         return *this;
     }
+    constexpr value_kind get_value_kind() const noexcept
+    {
+        return value_kind::boolean;
+    }
 };
 
 struct string_value final
@@ -157,6 +175,11 @@ struct string_value final
     {
     }
     static void write(std::ostream &os, const std::string &value, write_state &state);
+    static void write(std::ostream &os, const std::string &value)
+    {
+        write_state state(write_options::defaults());
+        write(os, value, state);
+    }
     void write(std::ostream &os, write_state &state) const
     {
         write(os, value, state);
@@ -173,6 +196,10 @@ struct string_value final
     {
         return *this;
     }
+    constexpr value_kind get_value_kind() const noexcept
+    {
+        return value_kind::string;
+    }
 };
 
 struct number_value final
@@ -267,6 +294,10 @@ struct number_value final
     {
         return *this;
     }
+    constexpr value_kind get_value_kind() const noexcept
+    {
+        return value_kind::number;
+    }
 };
 
 struct composite_value;
@@ -291,6 +322,16 @@ public:
     {
         return *value;
     }
+    const std::shared_ptr<composite_value> &get() const &noexcept
+    {
+        return value;
+    }
+    std::shared_ptr<composite_value> get() && noexcept
+    {
+        std::shared_ptr<composite_value> retval = nullptr;
+        retval.swap(value);
+        return retval;
+    }
 };
 
 typedef util::
@@ -306,6 +347,7 @@ struct composite_value
     {
         return duplicate();
     }
+    virtual value_kind get_value_kind() const noexcept = 0;
 };
 
 inline value duplicate(const value &v)
@@ -337,6 +379,10 @@ struct object final : public composite_value
         }
         return std::make_shared<object>(std::move(new_values));
     }
+    value_kind get_value_kind() const noexcept override
+    {
+        return value_kind::object;
+    }
 };
 
 struct array final : public composite_value
@@ -357,7 +403,21 @@ struct array final : public composite_value
             new_values.emplace_back(ast::duplicate(value));
         return std::make_shared<array>(std::move(new_values));
     }
+    value_kind get_value_kind() const noexcept override
+    {
+        return value_kind::array;
+    }
 };
+
+inline value_kind get_value_kind(const value &v) noexcept
+{
+    return util::visit(
+        [&](const auto &v) -> value_kind
+        {
+            return v->get_value_kind();
+        },
+        v);
+}
 }
 
 inline void write(std::ostream &os, const ast::value &v, write_state &state)
@@ -365,7 +425,7 @@ inline void write(std::ostream &os, const ast::value &v, write_state &state)
     util::visit(
         [&](const auto &v) -> void
         {
-            return v->write(os, state);
+            v->write(os, state);
         },
         v);
 }
index 46e589e762f99dfa33213c20ac71a69a4d230e7b..7f8ec70cdf745fb935789e3364d802b0941cddbe 100644 (file)
 # SOFTWARE.
 #
 cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
-set(sources spirv.cpp) 
+
+set(spirv_parser_generated_include_dir ${CMAKE_CURRENT_BINARY_DIR}/src)
+set(spirv_parser_generated_dir ${spirv_parser_generated_include_dir}/spirv)
+set(spirv_parser_source ${spirv_parser_generated_dir}/parser.cpp)
+set(spirv_parser_header ${spirv_parser_generated_dir}/parser.h)
+set(spirv_core_grammar_json ${CMAKE_CURRENT_SOURCE_DIR}/../khronos-spirv/spirv.core.grammar.json)
+
+add_custom_command(OUTPUT ${spirv_parser_source} ${spirv_parser_header}
+                   COMMAND ${CMAKE_COMMAND} -E make_directory ${spirv_parser_generated_dir}
+                   COMMAND ${CMAKE_COMMAND} -E chdir ${spirv_parser_generated_dir} $<TARGET_FILE:generate_spirv_parser> ${spirv_core_grammar_json}
+                   MAIN_DEPENDENCY ${spirv_core_grammar_json}
+                   DEPENDS $<TARGET_FILE:generate_spirv_parser>
+                   VERBATIM
+                   COMMENT "Generating SPIR-V Parser")
+
+set(sources spirv.cpp ${spirv_parser_source}) 
 add_library(spirv STATIC ${sources})
-target_link_libraries(spirv util)
\ No newline at end of file
+target_link_libraries(spirv util)
+target_include_directories(spirv PUBLIC ${spirv_parser_generated_include_dir})
\ No newline at end of file