From 148381034676fdc52d6f6368a9ddbbb64095c0a4 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 2 Jun 2017 19:37:19 -0700 Subject: [PATCH] working on implementing generate_spirv_parser::parser::parse --- src/generate_spirv_parser/CMakeLists.txt | 4 +- src/generate_spirv_parser/ast.cpp | 23 ++ src/generate_spirv_parser/ast.h | 69 ++++++ .../generate_spirv_parser.cpp | 30 +-- src/generate_spirv_parser/parser.cpp | 202 ++++++++++++++++++ src/generate_spirv_parser/parser.h | 116 ++++++++++ src/json/json.h | 62 +++++- src/spirv/CMakeLists.txt | 20 +- 8 files changed, 510 insertions(+), 16 deletions(-) create mode 100644 src/generate_spirv_parser/ast.cpp create mode 100644 src/generate_spirv_parser/ast.h create mode 100644 src/generate_spirv_parser/parser.cpp create mode 100644 src/generate_spirv_parser/parser.h diff --git a/src/generate_spirv_parser/CMakeLists.txt b/src/generate_spirv_parser/CMakeLists.txt index e2954a0..f55fd72 100644 --- a/src/generate_spirv_parser/CMakeLists.txt +++ b/src/generate_spirv_parser/CMakeLists.txt @@ -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 index 0000000..9e4b259 --- /dev/null +++ b/src/generate_spirv_parser/ast.cpp @@ -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 index 0000000..eedb5c7 --- /dev/null +++ b/src/generate_spirv_parser/ast.h @@ -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 + +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_ */ diff --git a/src/generate_spirv_parser/generate_spirv_parser.cpp b/src/generate_spirv_parser/generate_spirv_parser.cpp index e38b216..feaa3c8 100644 --- a/src/generate_spirv_parser/generate_spirv_parser.cpp +++ b/src/generate_spirv_parser/generate_spirv_parser.cpp @@ -23,13 +23,13 @@ #include #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 index 0000000..ee7595f --- /dev/null +++ b/src/generate_spirv_parser/parser.cpp @@ -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 +#include + +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(e)) + { + ss << util::get(e); + } + else + { + json::ast::string_value::write(ss, util::get(e)); + } + ss << ']'; + } + return ss.str(); +} + +namespace +{ +template +Value get_value_or_throw_parse_error(util::optional 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 ©right_array = + static_cast(*util::get(value)); + for(std::size_t index = 0; index < copyright_array.values.size(); index++) + { + path_builder 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 +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(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 +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(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::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(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( + *util::get(top_level_value)); + util::optional copyright; + util::optional magic_number; + util::optional major_version; + util::optional 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 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( + entry_value, &path_builder, "magic_number", 1, 8); + } + else if(key == "major_version") + { + major_version = parse_integer(entry_value, &path_builder, "major_version"); + } + else if(key == "minor_version") + { + minor_version = parse_integer(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 index 0000000..67e86bb --- /dev/null +++ b/src/generate_spirv_parser/parser.h @@ -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 +#include +#include +#include +#include "../util/variant.h" +#include "../json/json.h" + +namespace vulkan_cpu +{ +namespace generate_spirv_parser +{ +namespace parser +{ +struct path +{ + typedef util::variant element; + std::vector elements; + path() : elements() + { + } + path(std::vector elements) : elements(std::move(elements)) + { + } + path(std::initializer_list 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 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 +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_ */ diff --git a/src/json/json.h b/src/json/json.h index 2676a4e..8ac3070 100644 --- a/src/json/json.h +++ b/src/json/json.h @@ -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 &get() const &noexcept + { + return value; + } + std::shared_ptr get() && noexcept + { + std::shared_ptr 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(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(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); } diff --git a/src/spirv/CMakeLists.txt b/src/spirv/CMakeLists.txt index 46e589e..7f8ec70 100644 --- a/src/spirv/CMakeLists.txt +++ b/src/spirv/CMakeLists.txt @@ -19,6 +19,22 @@ # 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} $ ${spirv_core_grammar_json} + MAIN_DEPENDENCY ${spirv_core_grammar_json} + DEPENDS $ + 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 -- 2.30.2