From 18aa6ab630dae7f4e6a260fa0e1e43d0ef3e3916 Mon Sep 17 00:00:00 2001 From: Owen Smith Date: Fri, 16 Feb 2024 00:51:53 +0000 Subject: [PATCH] wip shaderc --- engine/CMakeLists.txt | 4 +- engine/include/vull/shaderc/arena.hh | 82 ++++++ engine/include/vull/shaderc/ast.hh | 389 ++++++++++++++++++++++++++ engine/include/vull/shaderc/parser.hh | 84 ++++++ engine/include/vull/shaderc/token.hh | 3 + engine/include/vull/shaderc/type.hh | 34 +++ engine/sources/CMakeLists.txt | 4 +- engine/sources/script/lexer.cc | 2 +- engine/sources/shaderc/ast.cc | 86 ++++++ engine/sources/shaderc/lexer.cc | 20 +- engine/sources/shaderc/parser.cc | 125 +++++++++ engine/tests/CMakeLists.txt | 2 + engine/tests/shaderc/lexer.cc | 2 +- engine/tests/shaderc/parse_errors.cc | 74 +++++ engine/tests/shaderc/parser.cc | 22 ++ tools/CMakeLists.txt | 2 +- tools/vslc/main.cc | 112 +++++--- 17 files changed, 1000 insertions(+), 47 deletions(-) create mode 100644 engine/include/vull/shaderc/arena.hh create mode 100644 engine/include/vull/shaderc/ast.hh create mode 100644 engine/include/vull/shaderc/parser.hh create mode 100644 engine/include/vull/shaderc/type.hh create mode 100644 engine/sources/shaderc/ast.cc create mode 100644 engine/sources/shaderc/parser.cc create mode 100644 engine/tests/shaderc/parse_errors.cc create mode 100644 engine/tests/shaderc/parser.cc diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 878c2a95..b4441379 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -21,7 +21,7 @@ add_shader(shaders/depth_reduce.comp) add_shader(shaders/draw_cull.comp) add_shader(shaders/fst.vert) add_shader(shaders/light_cull.comp) -add_shader(shaders/object.vsl) +#add_shader(shaders/object.vsl) add_shader(shaders/shadow.vert) add_shader(shaders/skybox.frag) add_shader(shaders/skybox.vert) @@ -40,7 +40,7 @@ function(vull_depend_builtin target) ${CMAKE_BINARY_DIR}/engine/shaders/draw_cull.comp.spv /shaders/draw_cull.comp ${CMAKE_BINARY_DIR}/engine/shaders/fst.vert.spv /shaders/fst.vert ${CMAKE_BINARY_DIR}/engine/shaders/light_cull.comp.spv /shaders/light_cull.comp - ${CMAKE_BINARY_DIR}/engine/shaders/object.vsl.spv /shaders/object +# ${CMAKE_BINARY_DIR}/engine/shaders/object.vsl.spv /shaders/object ${CMAKE_BINARY_DIR}/engine/shaders/shadow.vert.spv /shaders/shadow.vert ${CMAKE_BINARY_DIR}/engine/shaders/skybox.frag.spv /shaders/skybox.frag ${CMAKE_BINARY_DIR}/engine/shaders/skybox.vert.spv /shaders/skybox.vert diff --git a/engine/include/vull/shaderc/arena.hh b/engine/include/vull/shaderc/arena.hh new file mode 100644 index 00000000..954ec54c --- /dev/null +++ b/engine/include/vull/shaderc/arena.hh @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +#include +#include + +namespace vull::shaderc { + +class ArenaChunk { + static constexpr size_t k_size = 65536; + uint8_t *m_data; + size_t m_head{0}; + +public: + ArenaChunk() : m_data(new uint8_t[k_size]) {} + ArenaChunk(const ArenaChunk &) = delete; + ArenaChunk(ArenaChunk &&); + ~ArenaChunk() { delete[] m_data; } + + ArenaChunk &operator=(const ArenaChunk &) = delete; + ArenaChunk &operator=(ArenaChunk &&) = delete; + + void *allocate(size_t size, size_t alignment); +}; + +class Arena { + Vector m_chunks; + ArenaChunk *m_current_chunk; + +public: + Arena() : m_current_chunk(&m_chunks.emplace()) {} + Arena(const Arena &) = delete; + Arena(Arena &&); + ~Arena() = default; + + Arena &operator=(const Arena &) = delete; + Arena &operator=(Arena &&) = delete; + + template + U *allocate(Args &&...args); + template + void destroy(U *ptr); +}; + +inline ArenaChunk::ArenaChunk(ArenaChunk &&other) { + m_data = vull::exchange(other.m_data, nullptr); + m_head = vull::exchange(other.m_head, 0u); +} + +inline void *ArenaChunk::allocate(size_t size, size_t alignment) { + size = (size + alignment - 1) & ~(alignment - 1); + if (m_head + size >= k_size) { + return nullptr; + } + auto *ptr = m_data + m_head; + m_head += size; + return ptr; +} + +inline Arena::Arena(Arena &&other) { + m_chunks = vull::move(other.m_chunks); + m_current_chunk = vull::exchange(other.m_current_chunk, nullptr); +} + +template +U *Arena::allocate(Args &&...args) { + auto *ptr = m_current_chunk->allocate(sizeof(U), alignof(U)); + if (ptr == nullptr) { + m_current_chunk = &m_chunks.emplace(); + ptr = m_current_chunk->allocate(sizeof(U), alignof(U)); + } + return new (ptr) U(vull::forward(args)...); +} + +template +void Arena::destroy(U *ptr) { + ptr->~U(); +} + +} // namespace vull::shaderc diff --git a/engine/include/vull/shaderc/ast.hh b/engine/include/vull/shaderc/ast.hh new file mode 100644 index 00000000..f3f1171f --- /dev/null +++ b/engine/include/vull/shaderc/ast.hh @@ -0,0 +1,389 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace vull::shaderc::ast { + +enum class TraverseOrder { + None, + PreOrder, + PostOrder, +}; + +template +struct Traverser; + +struct Node { + virtual void traverse(Traverser &) = 0; + virtual void traverse(Traverser &) = 0; + virtual void traverse(Traverser &) = 0; + virtual Type type() const { VULL_ENSURE_NOT_REACHED(); } +}; + +class TypedNode : public Node { + Type m_type; + +public: + void set_type(const Type &type) { m_type = type; } + Type type() const final { return m_type; } +}; + +enum class AggregateKind { + Block, + ConstructExpr, + UniformBlock, +}; + +class Aggregate final : public TypedNode { + Vector m_nodes; + AggregateKind m_kind; + +public: + explicit Aggregate(AggregateKind kind) : m_kind(kind) {} + + void append_node(Node *node) { m_nodes.push(node); } + void traverse(Traverser &) override; + void traverse(Traverser &) override; + void traverse(Traverser &) override; + + AggregateKind kind() const { return m_kind; } + const Vector &nodes() const { return m_nodes; } +}; + +enum class BinaryOp { + Add, + Sub, + Mul, + Div, + Mod, + + Assign, + AddAssign, + SubAssign, + MulAssign, + DivAssign, + + // Parsed-generated Muls can be turned into these by the legaliser. + VectorTimesScalar, + MatrixTimesScalar, + VectorTimesMatrix, + MatrixTimesVector, + MatrixTimesMatrix, +}; + +class BinaryExpr final : public TypedNode { + Node *m_lhs; + Node *m_rhs; + BinaryOp m_op; + +public: + BinaryExpr(BinaryOp op, Node *lhs, Node *rhs) : m_lhs(lhs), m_rhs(rhs), m_op(op) {} + + void set_op(BinaryOp op) { m_op = op; } + void traverse(Traverser &) override; + void traverse(Traverser &) override; + void traverse(Traverser &) override; + + BinaryOp op() const { return m_op; } + Node &lhs() const { return *m_lhs; } + Node &rhs() const { return *m_rhs; } +}; + +class CallExpr final : public TypedNode { + StringView m_name; + Vector m_arguments; + +public: + explicit CallExpr(StringView name) : m_name(name) {} + + void append_argument(Node *argument) { m_arguments.push(argument); } + void traverse(Traverser &) override; + void traverse(Traverser &) override; + void traverse(Traverser &) override; + + StringView name() const { return m_name; } + const Vector &arguments() const { return m_arguments; } +}; + +class Constant final : public Node { + union { + float decimal; + size_t integer; + } m_literal; + ScalarType m_scalar_type; + +public: + explicit Constant(float decimal) : m_literal({.decimal = decimal}), m_scalar_type(ScalarType::Float) {} + explicit Constant(size_t integer) : m_literal({.integer = integer}), m_scalar_type(ScalarType::Uint) {} + + void traverse(Traverser &) override; + void traverse(Traverser &) override; + void traverse(Traverser &) override; + Type type() const override { return {m_scalar_type, 1, 1}; } + + float decimal() const { return m_literal.decimal; } + size_t integer() const { return m_literal.integer; } + ScalarType scalar_type() const { return m_scalar_type; } +}; + +class DeclStmt final : public Node { + StringView m_name; + Node *m_value; + +public: + DeclStmt(StringView name, Node *value) : m_name(name), m_value(value) {} + + void traverse(Traverser &) override; + void traverse(Traverser &) override; + void traverse(Traverser &) override; + + StringView name() const { return m_name; } + Node &value() const { return *m_value; } +}; + +class Parameter { + StringView m_name; + Type m_type; + +public: + Parameter(StringView name, const Type &type) : m_name(name), m_type(type) {} + + StringView name() const { return m_name; } + const Type &type() const { return m_type; } +}; + +class FunctionDecl final : public Node { + StringView m_name; + Aggregate *m_block; + Type m_return_type; + Vector m_parameters; + +public: + FunctionDecl(StringView name, Aggregate *block, const Type &return_type, Vector &¶meters) + : m_name(name), m_block(block), m_return_type(return_type), m_parameters(vull::move(parameters)) {} + + void traverse(Traverser &) override; + void traverse(Traverser &) override; + void traverse(Traverser &) override; + + StringView name() const { return m_name; } + Aggregate &block() const { return *m_block; } + const Type &return_type() const { return m_return_type; } + const Vector ¶meters() const { return m_parameters; } +}; + +class PipelineDecl final : public TypedNode { + StringView m_name; + +public: + PipelineDecl(StringView name, const Type &type) : m_name(name) { set_type(type); } + + void traverse(Traverser &) override; + void traverse(Traverser &) override; + void traverse(Traverser &) override; + + StringView name() const { return m_name; } +}; + +class ReturnStmt final : public Node { + Node *m_expr; + +public: + explicit ReturnStmt(Node *expr) : m_expr(expr) {} + + void traverse(Traverser &) override; + void traverse(Traverser &) override; + void traverse(Traverser &) override; + + Node &expr() const { return *m_expr; } +}; + +class Root final : public Node { + Arena m_arena; + Vector m_top_level_nodes; + +public: + Root() = default; + Root(const Root &) = delete; + Root(Root &&) = default; + ~Root(); + + Root &operator=(const Root &) = delete; + Root &operator=(Root &&) = delete; + + template + U *allocate(Args &&...args) { + return m_arena.allocate(vull::forward(args)...); + } + + void append_top_level(Node *node); + void traverse(Traverser &) override; + void traverse(Traverser &) override; + void traverse(Traverser &) override; + const Vector &top_level_nodes() const { return m_top_level_nodes; } +}; + +class Symbol final : public TypedNode { + StringView m_name; + +public: + explicit Symbol(StringView name) : m_name(name) {} + + void traverse(Traverser &) override; + void traverse(Traverser &) override; + void traverse(Traverser &) override; + + StringView name() const { return m_name; } +}; + +enum class UnaryOp { + Negate, +}; + +class UnaryExpr final : public TypedNode { + Node *m_expr; + UnaryOp m_op; + +public: + UnaryExpr(UnaryOp op, Node *expr) : m_expr(expr), m_op(op) {} + + void traverse(Traverser &) override; + void traverse(Traverser &) override; + void traverse(Traverser &) override; + + UnaryOp op() const { return m_op; } + Node &expr() const { return *m_expr; } +}; + +template +struct Traverser { + virtual void visit(Aggregate &) = 0; + virtual void visit(BinaryExpr &) = 0; + virtual void visit(CallExpr &) = 0; + virtual void visit(Constant &) = 0; + virtual void visit(DeclStmt &) = 0; + virtual void visit(FunctionDecl &) = 0; + virtual void visit(PipelineDecl &) = 0; + virtual void visit(ReturnStmt &) = 0; + virtual void visit(Root &) = 0; + virtual void visit(Symbol &) = 0; + virtual void visit(UnaryExpr &) = 0; +}; + +#define DEFINE_SIMPLE_TRAVERSE(node) \ + inline void node::traverse(Traverser &traverser) { \ + traverser.visit(*this); \ + } \ + inline void node::traverse(Traverser &traverser) { \ + traverser.visit(*this); \ + } \ + inline void node::traverse(Traverser &traverser) { \ + traverser.visit(*this); \ + } + +// Aggregates and functions usually require special handling. +DEFINE_SIMPLE_TRAVERSE(Aggregate) +DEFINE_SIMPLE_TRAVERSE(CallExpr) +DEFINE_SIMPLE_TRAVERSE(Constant) +DEFINE_SIMPLE_TRAVERSE(FunctionDecl) +DEFINE_SIMPLE_TRAVERSE(PipelineDecl) +DEFINE_SIMPLE_TRAVERSE(Symbol) +#undef DEFINE_SIMPLE_TRAVERSE + +inline void BinaryExpr::traverse(Traverser &traverser) { + traverser.visit(*this); +} + +inline void BinaryExpr::traverse(Traverser &traverser) { + traverser.visit(*this); + m_lhs->traverse(traverser); + m_rhs->traverse(traverser); +} + +inline void BinaryExpr::traverse(Traverser &traverser) { + m_lhs->traverse(traverser); + m_rhs->traverse(traverser); + traverser.visit(*this); +} + +inline void DeclStmt::traverse(Traverser &traverser) { + traverser.visit(*this); +} + +inline void DeclStmt::traverse(Traverser &traverser) { + traverser.visit(*this); + m_value->traverse(traverser); +} + +inline void DeclStmt::traverse(Traverser &traverser) { + m_value->traverse(traverser); + traverser.visit(*this); +} + +inline void ReturnStmt::traverse(Traverser &traverser) { + traverser.visit(*this); +} + +inline void ReturnStmt::traverse(Traverser &traverser) { + traverser.visit(*this); + m_expr->traverse(traverser); +} + +inline void ReturnStmt::traverse(Traverser &traverser) { + m_expr->traverse(traverser); + traverser.visit(*this); +} + +inline void Root::traverse(Traverser &traverser) { + traverser.visit(*this); +} + +inline void Root::traverse(Traverser &traverser) { + traverser.visit(*this); + for (auto *node : m_top_level_nodes) { + node->traverse(traverser); + } +} + +inline void Root::traverse(Traverser &traverser) { + for (auto *node : m_top_level_nodes) { + node->traverse(traverser); + } + traverser.visit(*this); +} + +inline void UnaryExpr::traverse(Traverser &traverser) { + traverser.visit(*this); +} + +inline void UnaryExpr::traverse(Traverser &traverser) { + traverser.visit(*this); + m_expr->traverse(traverser); +} + +inline void UnaryExpr::traverse(Traverser &traverser) { + m_expr->traverse(traverser); + traverser.visit(*this); +} + +constexpr bool is_assign_op(BinaryOp op) { + switch (op) { + case BinaryOp::Assign: + case BinaryOp::AddAssign: + case BinaryOp::SubAssign: + case BinaryOp::MulAssign: + case BinaryOp::DivAssign: + return true; + default: + return false; + } +} + +} // namespace vull::shaderc::ast diff --git a/engine/include/vull/shaderc/parser.hh b/engine/include/vull/shaderc/parser.hh new file mode 100644 index 00000000..945e1f02 --- /dev/null +++ b/engine/include/vull/shaderc/parser.hh @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace vull::shaderc { + +class Lexer; + +class ParseMessage { +public: + enum class Kind { + Error, + Note, + }; + +private: + Token m_token; + String m_text; + Kind m_kind; + +public: + ParseMessage() = default; + ParseMessage(Kind kind, const Token &token, String &&text) + : m_token(token), m_text(vull::move(text)), m_kind(kind) {} + + const Token &token() const { return m_token; } + const String &text() const { return m_text; } + Kind kind() const { return m_kind; } +}; + +class ParseError { + // TODO(small-vector) + Vector m_messages; + +public: + ParseError() = default; + ParseError(const ParseError &other) { + m_messages.extend(other.m_messages); + } + ParseError(ParseError &&) = default; + ~ParseError() = default; + + ParseError &operator=(const ParseError &) = delete; + ParseError &operator=(ParseError &&) = delete; + + void add_error(const Token &token, String &&message); + void add_note(const Token &token, String &&message); + + const Vector &messages() const { return m_messages; } +}; + +class Parser { + Lexer &m_lexer; + ast::Root m_root; + HashMap m_builtin_type_map; + + Optional consume(TokenKind kind); + Result expect(TokenKind kind); + Result expect_semi(const Token &previous_token, StringView entity_name); + + Result parse_type(); + Result parse_atom(); + Result parse_expr(); + Result parse_stmt(); + Result parse_block(); + Result parse_function_decl(); + Result parse_pipeline_decl(); + Result parse_uniform_block(); + Result parse_top_level(); + +public: + explicit Parser(Lexer &lexer); + + Result parse(); +}; + +} // namespace vull::shaderc diff --git a/engine/include/vull/shaderc/token.hh b/engine/include/vull/shaderc/token.hh index afb576c8..5646e79c 100644 --- a/engine/include/vull/shaderc/token.hh +++ b/engine/include/vull/shaderc/token.hh @@ -53,6 +53,9 @@ public: StringView string() const; String to_string() const; + Token advanced(size_t amount) const; + size_t length() const; + TokenKind kind() const { return m_kind; } uint32_t position() const { return m_position; } uint16_t line() const { return m_line; } diff --git a/engine/include/vull/shaderc/type.hh b/engine/include/vull/shaderc/type.hh new file mode 100644 index 00000000..b5795445 --- /dev/null +++ b/engine/include/vull/shaderc/type.hh @@ -0,0 +1,34 @@ +#pragma once + +#include + +namespace vull::shaderc { + +enum class ScalarType : uint8_t { + Float, + Invalid, + Uint, + Void, +}; + +class Type { + ScalarType m_scalar_type{ScalarType::Invalid}; + uint8_t m_vector_size : 4; + uint8_t m_matrix_cols : 4; + +public: + Type() = default; + Type(ScalarType scalar_type, uint8_t vector_size = 1, uint8_t matrix_cols = 1) + : m_scalar_type(scalar_type), m_vector_size(vector_size), m_matrix_cols(matrix_cols) {} + + bool is_matrix() const { return m_matrix_cols > 1; } + bool is_vector() const { return !is_matrix() && m_vector_size > 1; } + bool is_scalar() const { return !is_matrix() && !is_vector(); } + + ScalarType scalar_type() const { return m_scalar_type; } + uint8_t vector_size() const { return m_vector_size; } + uint8_t matrix_cols() const { return m_matrix_cols; } + uint8_t matrix_rows() const { return m_vector_size; } +}; + +} // namespace vull::shaderc diff --git a/engine/sources/CMakeLists.txt b/engine/sources/CMakeLists.txt index 10acd894..b25f186b 100644 --- a/engine/sources/CMakeLists.txt +++ b/engine/sources/CMakeLists.txt @@ -55,7 +55,9 @@ target_sources(vull-script PRIVATE script/vm.cc) target_sources(vull-shaderc PRIVATE - shaderc/lexer.cc) + shaderc/ast.cc + shaderc/lexer.cc + shaderc/parser.cc) target_sources(vull-ui PRIVATE ui/layout/box_layout.cc diff --git a/engine/sources/script/lexer.cc b/engine/sources/script/lexer.cc index a59ab4a0..0e09b3cf 100644 --- a/engine/sources/script/lexer.cc +++ b/engine/sources/script/lexer.cc @@ -114,7 +114,7 @@ Token Lexer::next_token() { SourcePosition Lexer::recover_position(const Token &token) const { // Backtrack to find line start. uint32_t line_head = token.position(); - while (line_head > 1 && m_source[line_head - 1] != '\n') { + while (line_head > 0 && m_source[line_head - 1] != '\n') { line_head--; } diff --git a/engine/sources/shaderc/ast.cc b/engine/sources/shaderc/ast.cc new file mode 100644 index 00000000..5f73cade --- /dev/null +++ b/engine/sources/shaderc/ast.cc @@ -0,0 +1,86 @@ +#include + +#include +#include + +namespace vull::shaderc::ast { +namespace { + +class DestroyVisitor final : public Traverser { + Arena &m_arena; + +public: + explicit DestroyVisitor(Arena &arena) : m_arena(arena) {} + + void visit(Aggregate &) override; + void visit(BinaryExpr &) override; + void visit(CallExpr &) override; + void visit(Constant &) override; + void visit(DeclStmt &) override; + void visit(FunctionDecl &) override; + void visit(PipelineDecl &) override; + void visit(ReturnStmt &) override; + void visit(Symbol &) override; + void visit(Root &) override {} + void visit(UnaryExpr &) override; +}; + +void DestroyVisitor::visit(Aggregate &aggregate) { + for (auto *node : aggregate.nodes()) { + node->traverse(*this); + } + m_arena.destroy(&aggregate); +} + +void DestroyVisitor::visit(BinaryExpr &binary_expr) { + m_arena.destroy(&binary_expr); +} + +void DestroyVisitor::visit(CallExpr &call_expr) { + for (auto *argument : call_expr.arguments()) { + argument->traverse(*this); + } + m_arena.destroy(&call_expr); +} + +void DestroyVisitor::visit(Constant &constant) { + m_arena.destroy(&constant); +} + +void DestroyVisitor::visit(DeclStmt &decl_stmt) { + m_arena.destroy(&decl_stmt); +} + +void DestroyVisitor::visit(FunctionDecl &function_decl) { + function_decl.block().traverse(*this); + m_arena.destroy(&function_decl); +} + +void DestroyVisitor::visit(PipelineDecl &pipeline_decl) { + m_arena.destroy(&pipeline_decl); +} + +void DestroyVisitor::visit(ReturnStmt &return_stmt) { + m_arena.destroy(&return_stmt); +} + +void DestroyVisitor::visit(Symbol &symbol) { + m_arena.destroy(&symbol); +} + +void DestroyVisitor::visit(UnaryExpr &unary_expr) { + m_arena.destroy(&unary_expr); +} + +} // namespace + +Root::~Root() { + DestroyVisitor destroy_visitor(m_arena); + traverse(destroy_visitor); +} + +void Root::append_top_level(Node *node) { + m_top_level_nodes.push(node); +} + +} // namespace vull::shaderc::ast diff --git a/engine/sources/shaderc/lexer.cc b/engine/sources/shaderc/lexer.cc index 789c7933..d6eed7f4 100644 --- a/engine/sources/shaderc/lexer.cc +++ b/engine/sources/shaderc/lexer.cc @@ -107,7 +107,7 @@ Token Lexer::next_token() { SourcePosition Lexer::recover_position(const Token &token) const { // Backtrack to find line start. uint32_t line_head = token.position(); - while (line_head > 1 && m_source[line_head - 1] != '\n') { + while (line_head > 0 && m_source[line_head - 1] != '\n') { line_head--; } @@ -189,4 +189,22 @@ String Token::to_string() const { } } +Token Token::advanced(size_t amount) const { + auto token = *this; + token.m_position += amount; + return token; +} + +size_t Token::length() const { + switch (m_kind) { + case TokenKind::Invalid: + case TokenKind::Eof: + return 0; + case TokenKind::Identifier: + return string().length(); + default: + return 1; + } +} + } // namespace vull::shaderc diff --git a/engine/sources/shaderc/parser.cc b/engine/sources/shaderc/parser.cc new file mode 100644 index 00000000..44525579 --- /dev/null +++ b/engine/sources/shaderc/parser.cc @@ -0,0 +1,125 @@ +#include + +#include +#include + +namespace vull::shaderc { + +void ParseError::add_error(const Token &token, String &&message) { + m_messages.emplace(ParseMessage::Kind::Error, token, vull::move(message)); +} + +void ParseError::add_note(const Token &token, String &&message) { + m_messages.emplace(ParseMessage::Kind::Note, token, vull::move(message)); +} + +Parser::Parser(Lexer &lexer) : m_lexer(lexer) { + m_builtin_type_map.set("float", ScalarType::Float); + m_builtin_type_map.set("vec2", {ScalarType::Float, 2}); + m_builtin_type_map.set("vec3", {ScalarType::Float, 3}); + m_builtin_type_map.set("vec4", {ScalarType::Float, 4}); + m_builtin_type_map.set("mat3", {ScalarType::Float, 3, 3}); + m_builtin_type_map.set("mat4", {ScalarType::Float, 4, 4}); +} + +Optional Parser::consume(TokenKind kind) { + const auto &token = m_lexer.peek(); + return token.kind() == kind ? m_lexer.next() : Optional(); +} + +Result Parser::expect(TokenKind kind) { + auto token = m_lexer.next(); + if (token.kind() != kind) { + ParseError error; + error.add_error(token, vull::format("expected {} but got {}", Token::kind_string(kind), token.to_string())); + return error; + } + return token; +} + +Result Parser::expect_semi(const Token &previous_token, StringView entity_name) { + auto token = m_lexer.next(); + if (token.kind() != ';'_tk) { + ParseError error; + error.add_error(previous_token.advanced(previous_token.length()), + vull::format("missing ';' after {}", entity_name)); + error.add_note(token, vull::format("expected ';' before {}", token.to_string())); + return error; + } + return {}; +} + +Result Parser::parse_type() { + auto token = m_lexer.next(); + if (token.kind() != TokenKind::Identifier) { + ParseError error; + error.add_error(token, "expected type name"); + return error; + } + + auto builtin_type = m_builtin_type_map.get(token.string()); + if (!builtin_type) { + ParseError error; + error.add_error(token, vull::format("unknown type name '{}'", token.string())); + return error; + } + return *builtin_type; +} + +Result Parser::parse_function_decl() { + auto name = VULL_TRY(expect(TokenKind::Identifier)); + + + // return nullptr; + VULL_ENSURE_NOT_REACHED(); +} + +Result Parser::parse_pipeline_decl() { + auto type = VULL_TRY(parse_type()); + auto name = VULL_TRY(expect(TokenKind::Identifier)); + VULL_TRY(expect_semi(name, "pipeline declaration")); + return m_root.allocate(name.string(), type); +} + +Result Parser::parse_uniform_block() { + // TODO: Expect '{' to open uniform block. + VULL_TRY(expect('{'_tk)); + auto *block = m_root.allocate(ast::AggregateKind::UniformBlock); + Optional closing_brace; + while (!(closing_brace = consume('}'_tk))) { + auto name = VULL_TRY(expect(TokenKind::Identifier)); + VULL_TRY(expect(':'_tk)); + auto type = VULL_TRY(parse_type()); + auto *symbol = m_root.allocate(name.string()); + symbol->set_type(type); + block->append_node(symbol); + VULL_TRY(expect(','_tk)); + } + VULL_TRY(expect_semi(*closing_brace, "uniform block declaration")); + return block; +} + +Result Parser::parse_top_level() { + if (consume(TokenKind::KW_fn)) { + return static_cast(VULL_TRY(parse_function_decl())); + } + if (consume(TokenKind::KW_pipeline)) { + return static_cast(VULL_TRY(parse_pipeline_decl())); + } + if (consume(TokenKind::KW_uniform)) { + return static_cast(VULL_TRY(parse_uniform_block())); + } + + ParseError error; + error.add_error(m_lexer.next(), "expected top level declaration"); + return error; +} + +Result Parser::parse() { + while (!consume(TokenKind::Eof)) { + m_root.append_top_level(VULL_TRY(parse_top_level())); + } + return vull::move(m_root); +} + +} // namespace vull::shaderc diff --git a/engine/tests/CMakeLists.txt b/engine/tests/CMakeLists.txt index 3905a46b..b2a45023 100644 --- a/engine/tests/CMakeLists.txt +++ b/engine/tests/CMakeLists.txt @@ -10,6 +10,8 @@ target_sources(vull-tests PRIVATE maths/relational.cc script/lexer.cc shaderc/lexer.cc + shaderc/parse_errors.cc + shaderc/parser.cc support/enum.cc support/variant.cc ui/units.cc diff --git a/engine/tests/shaderc/lexer.cc b/engine/tests/shaderc/lexer.cc index 530cba00..070240b6 100644 --- a/engine/tests/shaderc/lexer.cc +++ b/engine/tests/shaderc/lexer.cc @@ -8,7 +8,7 @@ #include using namespace vull; -using vull::shaderc::operator""_tk; // NOLINT +using vull::shaderc::operator""_tk; TEST_CASE(ShaderLexer, Empty) { shaderc::Lexer lexer("", ""); diff --git a/engine/tests/shaderc/parse_errors.cc b/engine/tests/shaderc/parse_errors.cc new file mode 100644 index 00000000..193594a9 --- /dev/null +++ b/engine/tests/shaderc/parse_errors.cc @@ -0,0 +1,74 @@ +#include + +#include +#include +#include +#include +#include +#include + +using namespace vull; +using vull::shaderc::operator""_tk; + +static shaderc::ParseError try_parse(StringView source) { + shaderc::Lexer lexer("", source); + shaderc::Parser parser(lexer); + auto result = parser.parse(); + EXPECT(result.is_error()); + return result.error(); +} + +static bool has_message(const shaderc::ParseError &error, shaderc::ParseMessage::Kind kind, StringView text, + shaderc::TokenKind token_kind) { + for (const auto &message : error.messages()) { + if (message.kind() == kind && message.text().view() == text && message.token().kind() == token_kind) { + return true; + } + } + return false; +} + +static bool has_error(const shaderc::ParseError &error, StringView text, shaderc::TokenKind token_kind) { + return has_message(error, shaderc::ParseMessage::Kind::Error, text, token_kind); +} + +static bool has_note(const shaderc::ParseError &error, StringView text, shaderc::TokenKind token_kind) { + return has_message(error, shaderc::ParseMessage::Kind::Note, text, token_kind); +} + +TEST_CASE(ShaderParseErrors, BadTopLevel) { + auto parse_error = try_parse("foo"); + EXPECT(has_error(parse_error, "expected top level declaration", shaderc::TokenKind::Identifier)); +} + +TEST_CASE(ShaderParseErrors, FunctionDeclBadName) { + +} + +TEST_CASE(ShaderParseErrors, PipelineDeclBadType) { + auto parse_error = try_parse("pipeline 123 g_foo;"); + EXPECT(has_error(parse_error, "expected type name", shaderc::TokenKind::IntLit)); +} +TEST_CASE(ShaderParseErrors, PipelineDeclUnknownType) { + auto parse_error = try_parse("pipeline footype g_foo;"); + EXPECT(has_error(parse_error, "unknown type name 'footype'", shaderc::TokenKind::Identifier)); +} +TEST_CASE(ShaderParseErrors, PipelineDeclBadName) { + auto parse_error = try_parse("pipeline vec2 123;"); + EXPECT(has_error(parse_error, "expected identifier but got '123u'", shaderc::TokenKind::IntLit)); +} +TEST_CASE(ShaderParseErrors, PipelineDeclMissingSemicolon) { + auto parse_error = try_parse("pipeline vec3 g_foo"); + EXPECT(has_error(parse_error, "missing ';' after pipeline declaration", shaderc::TokenKind::Identifier)); + EXPECT(has_note(parse_error, "expected ';' before ", shaderc::TokenKind::Eof)); +} + +TEST_CASE(ShaderParseErrors, UniformBlockDeclMissingSemicolon) { + auto parse_error = try_parse(R"( +uniform { + g_transform: mat4, +} +)"); + EXPECT(has_error(parse_error, "missing ';' after uniform block declaration", '}'_tk)); + EXPECT(has_note(parse_error, "expected ';' before ", shaderc::TokenKind::Eof)); +} diff --git a/engine/tests/shaderc/parser.cc b/engine/tests/shaderc/parser.cc new file mode 100644 index 00000000..aff585de --- /dev/null +++ b/engine/tests/shaderc/parser.cc @@ -0,0 +1,22 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace vull; + +static Result try_parse(StringView source) { + shaderc::Lexer lexer("", source); + shaderc::Parser parser(lexer); + return parser.parse(); +} + +TEST_CASE(ShaderParser, Empty) { + auto result = try_parse(""); + EXPECT(!result.is_error()); +} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 3d5df618..fa549c6d 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -42,7 +42,7 @@ endif() vull_executable(vsi vull::core vull::script) target_sources(vsi PRIVATE vsi/main.cc) -vull_executable(vslc vull::core) +vull_executable(vslc vull::core vull::shaderc) target_sources(vslc PRIVATE vslc/spv/backend.cc vslc/spv/builder.cc diff --git a/tools/vslc/main.cc b/tools/vslc/main.cc index 5c38aa1f..6ee67d01 100644 --- a/tools/vslc/main.cc +++ b/tools/vslc/main.cc @@ -1,58 +1,90 @@ -#include "ast.hh" -#include "char_stream.hh" -#include "legaliser.hh" -#include "lexer.hh" -#include "parser.hh" -#include "spv/backend.hh" -#include "spv/builder.hh" -#include "spv/spirv.hh" - +#include +#include +#include #include #include +#include +#include +#include #include +#include #include -#include +#include +#include + +using namespace vull; -static void print_usage(const char *executable) { - fprintf(stderr, "usage: %s \n", executable); +static void print_message(shaderc::Lexer &lexer, const shaderc::ParseMessage &message) { + StringView kind_string = + message.kind() == shaderc::ParseMessage::Kind::Error ? "\x1b[1;91merror" : "\x1b[1;35mnote"; + const auto [file_name, line_source, line, column] = lexer.recover_position(message.token()); + vull::println("\x1b[1;37m{}:{}:{}: {}: \x1b[1;37m{}\x1b[0m", file_name, line, column, kind_string, message.text()); + vull::print(" { 4 } | {}\n |", line, line_source); + for (uint32_t i = 0; i < column; i++) { + vull::print(" "); + } + vull::println("\x1b[1;92m^\x1b[0m"); } int main(int argc, char **argv) { - const char *input_path = nullptr; - const char *output_path = nullptr; - for (int i = 1; i < argc; i++) { - if (input_path == nullptr) { - input_path = argv[i]; - } else if (output_path == nullptr) { - output_path = argv[i]; + Vector args(argv, argv + argc); + if (args.size() < 2) { + vull::println("usage: {} ", args[0]); + return EXIT_SUCCESS; + } + + StringView input_path; + StringView output_path; + for (const auto &arg : vull::slice(args, 1u)) { + if (input_path.empty()) { + input_path = arg; + } else if (output_path.empty()) { + output_path = arg; } else { - fprintf(stderr, "Invalid argument %s\n", argv[i]); - print_usage(argv[0]); - return 1; + vull::println("error: unexpected argument {}", arg); + return EXIT_FAILURE; } } - if (input_path == nullptr || output_path == nullptr) { - print_usage(argv[0]); - return 1; + if (input_path.empty() || output_path.empty()) { + vull::println("usage: {} ", args[0]); + return EXIT_FAILURE; } - CharStream char_stream(input_path); - Lexer lexer(vull::move(char_stream)); - Parser parser(lexer); - auto ast = parser.parse(); - - Legaliser legaliser; - ast.traverse(legaliser); + auto file = VULL_EXPECT(vull::open_file(input_path, OpenMode::Read)); + auto stream = file.create_stream(); + StringBuilder sb; + while (true) { + Array data; + auto bytes_read = static_cast(VULL_EXPECT(stream.read(data.span()))); + if (bytes_read == 0) { + break; + } + sb.extend(data.span().subspan(0, bytes_read)); + } - spv::Backend backend; - ast.traverse(backend); + shaderc::Lexer lexer(input_path, sb.build()); + shaderc::Parser parser(lexer); + auto ast_or_error = parser.parse(); + if (ast_or_error.is_error()) { + const auto &error = ast_or_error.error(); + for (const auto &message : error.messages()) { + print_message(lexer, message); + } + return EXIT_FAILURE; + } - auto output_file = VULL_EXPECT( - vull::open_file(output_path, vull::OpenMode::Create | vull::OpenMode::Truncate | vull::OpenMode::Write)); - auto output_stream = output_file.create_stream(); - backend.builder().write([&](spv::Word word) { - VULL_EXPECT(output_stream.write_le(word)); - }); + // Legaliser legaliser; + // ast.traverse(legaliser); + // + // spv::Backend backend; + // ast.traverse(backend); + // + // auto output_file = VULL_EXPECT( + // vull::open_file(output_path, vull::OpenMode::Create | vull::OpenMode::Truncate | vull::OpenMode::Write)); + // auto output_stream = output_file.create_stream(); + // backend.builder().write([&](spv::Word word) { + // VULL_EXPECT(output_stream.write_le(word)); + // }); }