Experiment: DNA: makesdna parser #118532

Open
Guillermo Venegas wants to merge 29 commits from guishe/blender:dna-parser into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
8 changed files with 1718 additions and 286 deletions

View File

@ -1122,7 +1122,7 @@ typedef enum eDirEntry_SelectFlag {
ENUM_OPERATORS(eDirEntry_SelectFlag, FILE_SEL_EDITING);
/* ***** Related to file browser, but never saved in DNA, only here to help with RNA. ***** */
#define DATETIME_STR_SIZE 16 + 8
#
#
typedef struct FileDirEntry {
@ -1139,7 +1139,7 @@ typedef struct FileDirEntry {
struct {
/* Temp caching of UI-generated strings. */
char size_str[16];
char datetime_str[16 + 8];
char datetime_str[DATETIME_STR_SIZE];

Although it is ignored by the parser, even the ignored structures are parsed then discarded, expressions like sums would not be supported to parse

Although it is ignored by the parser, even the ignored structures are parsed then discarded, expressions like sums would not be supported to parse
} draw_data;
/** #eFileSel_File_Types. */

View File

@ -18,6 +18,7 @@ set(INC_SYS
set(LIB
PRIVATE bf::intern::atomic
PRIVATE bf::intern::guardedalloc
PRIVATE bf::extern::fmtlib
)
add_definitions(-DWITH_DNA_GHASH)
@ -67,6 +68,8 @@ set(SRC_BLENLIB
set(SRC
dna_utils.cc
dna_lexer.cc
dna_parser.cc
makesdna.cc
${SRC_BLENLIB}
../../../../intern/guardedalloc/intern/leak_detector.cc
@ -123,6 +126,8 @@ add_custom_command(
set(SRC
dna_defaults.c
dna_genfile.cc
dna_lexer.cc
dna_parser.cc
dna_utils.cc
${CMAKE_CURRENT_BINARY_DIR}/dna.c
${CMAKE_CURRENT_BINARY_DIR}/dna_verify.c
@ -130,6 +135,8 @@ set(SRC
${CMAKE_CURRENT_BINARY_DIR}/dna_type_offsets.h
dna_rename_defs.h
dna_lexer.hh
dna_parser.hh
dna_utils.h
)
@ -164,6 +171,16 @@ set(SRC
set(LIB
PRIVATE bf::intern::atomic
PRIVATE bf::intern::guardedalloc
PRIVATE bf::extern::fmtlib
)
blender_add_lib(bf_dna_blenlib "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
set(TEST_INC
)
set(TEST_SRC
dna_parser_test.cc
)
blender_add_test_suite_lib(dna "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB}")
endif()

View File

@ -0,0 +1,317 @@
#include "dna_lexer.hh"
#include <cctype>
#include <charconv>
#include <fmt/format.h>
namespace blender::dna::lex {
static std::string_view string_view_from_range(const std::string_view::iterator first,
const std::string_view::iterator last)
{
return std::string_view{&*first, size_t(last - first)};
}
/* Match any white space except break lines. */
static void eval_space(std::string_view::iterator &itr,
std::string_view::iterator last,
TokenIterator & /*cont*/)
{
while (itr < last && itr[0] != '\n' && std::isspace(itr[0])) {
itr++;
}
}
/* Match break lines, added as tokens as they are needed for `#define` blocks. */
static void eval_break_line(std::string_view::iterator &itr,
std::string_view::iterator /*last*/,
TokenIterator &cont)
{
if (itr[0] != '\n') {
return;
}
cont.append(BreakLineToken{string_view_from_range(itr, itr + 1)});
itr++;
}
/* Match any identifier substring, also matches C++ keywords. */
static void eval_identifier(std::string_view::iterator &itr,
std::string_view::iterator last,
TokenIterator &cont)
{
if (!(std::isalpha(itr[0]) || itr[0] == '_')) {
return;
}
std::string_view::iterator start{itr++};
while (itr < last && (std::isalnum(itr[0]) || itr[0] == '_')) {
itr++;
}
struct KeywordItem {
std::string_view word;
KeywordType type;
};
using namespace std::string_view_literals;
static constexpr KeywordItem keywords[]{
{"BLI_STATIC_ASSERT_ALIGN"sv, KeywordType::BLI_STATIC_ASSERT_ALIGN},
{"DNA_DEFINE_CXX_METHODS"sv, KeywordType::DNA_DEFINE_CXX_METHODS},
{"DNA_DEPRECATED"sv, KeywordType::DNA_DEPRECATED},
{"DNA_DEPRECATED_ALLOW"sv, KeywordType::DNA_DEPRECATED_ALLOW},
{"ENUM_OPERATORS"sv, KeywordType::ENUM_OPERATORS},
{"extern"sv, KeywordType::EXTERN},
{"char"sv, KeywordType::CHAR},
{"char16_t"sv, KeywordType::CHAR16_T},
{"char32_t"sv, KeywordType::CHAR32_T},
{"class"sv, KeywordType::CLASS},
{"const"sv, KeywordType::CONST},
{"define"sv, KeywordType::DEFINE},
{"double"sv, KeywordType::DOUBLE},
{"endif"sv, KeywordType::ENDIF},
{"enum"sv, KeywordType::ENUM},
{"float"sv, KeywordType::FLOAT},
{"if"sv, KeywordType::IF},
{"ifdef"sv, KeywordType::IFDEF},
{"ifndef"sv, KeywordType::IFNDEF},
{"include"sv, KeywordType::INCLUDE},
{"int"sv, KeywordType::INT},
{"int16_t"sv, KeywordType::INT16_T},
{"int32_t"sv, KeywordType::INT32_T},
{"int64_t"sv, KeywordType::INT64_T},
{"int8_t"sv, KeywordType::INT8_T},
{"long"sv, KeywordType::LONG},
{"ulong"sv, KeywordType::ULONG},
{"once"sv, KeywordType::ONCE},
{"pragma"sv, KeywordType::PRAGMA},
{"private"sv, KeywordType::PRIVATE},
{"public"sv, KeywordType::PUBLIC},
{"short"sv, KeywordType::SHORT},
{"signed"sv, KeywordType::SIGNED},
{"struct"sv, KeywordType::STRUCT},
{"typedef"sv, KeywordType::TYPEDEF},
{"uint16_t"sv, KeywordType::UINT16_T},
{"uint32_t"sv, KeywordType::UINT32_T},
{"uint64_t"sv, KeywordType::UINT64_T},
{"uint8_t"sv, KeywordType::UINT8_T},
{"unsigned"sv, KeywordType::UNSIGNED},
{"void"sv, KeywordType::VOID},
};
std::string_view str = string_view_from_range(start, itr);
auto test_keyword_fn = [str](const KeywordItem &val) -> bool { return val.word == str; };
const KeywordItem *keyword_itr = std::find_if(
std::begin(keywords), std::end(keywords), test_keyword_fn);
if (keyword_itr != std::end(keywords)) {
cont.append(KeywordToken{str, keyword_itr->type});
return;
}
cont.append(IdentifierToken{str});
}
/* Match a line comment until break line. */
static void eval_line_comment(std::string_view::iterator &itr,
std::string_view::iterator last,
TokenIterator & /*cont*/)
{
if (last - itr < 2) {
return;
}
if (!(itr[0] == '/' && itr[1] == '/')) {
return;
}
while (itr != last && itr[0] != '\n') {
itr++;
}
}
/* Match a int literal. */
static void eval_int_literal(std::string_view::iterator &itr,
std::string_view::iterator last,
TokenIterator &cont)
{
const std::string_view::iterator start{itr};
while (itr < last && std::isdigit(itr[0])) {
itr++;
}
if (itr == start) {
return;
}
int val{};
std::from_chars(&*start, &*itr, val);
cont.append(IntLiteralToken{string_view_from_range(start, itr), val});
}
/* Match a c-style comment. */
static void eval_multiline_comment(std::string_view::iterator &itr,
std::string_view::iterator last,
TokenIterator & /*cont*/)
{
if (last - itr < +2) {
return;
}
if (!(itr[0] == '/' && itr[1] == '*')) {
return;
}
char carry = itr[0];
itr += 2;
while (itr < last && !(carry == '*' && itr[0] == '/')) {
carry = itr[0];
itr++;
}
if (itr < last) {
itr++;
}
}
/* Match a symbol. */
static void eval_symbol(std::string_view::iterator &itr,
std::string_view::iterator /*last*/,
TokenIterator &cont)
{
struct SymbolItem {
char value;
SymbolType type;
};
static constexpr SymbolItem symbols[]{
{'!', SymbolType::EXCLAMATION}, {'#', SymbolType::HASH}, {'%', SymbolType::PERCENT},
{'&', SymbolType::BIT_AND}, {'(', SymbolType::LPAREN}, {')', SymbolType::RPAREN},
{'*', SymbolType::STAR}, {'+', SymbolType::PLUS}, {',', SymbolType::COMMA},
{'-', SymbolType::MINUS}, {'.', SymbolType::DOT}, {'/', SymbolType::SLASH},
{':', SymbolType::COLON}, {';', SymbolType::SEMICOLON}, {'<', SymbolType::LESS},
{'=', SymbolType::ASSIGN}, {'>', SymbolType::GREATER}, {'?', SymbolType::QUESTION},
{'[', SymbolType::LBRACKET}, {'\\', SymbolType::BACKSLASH}, {']', SymbolType::RBRACKET},
{'^', SymbolType::CARET}, {'{', SymbolType::LBRACE}, {'|', SymbolType::BIT_OR},
{'}', SymbolType::RBRACE}, {'~', SymbolType::TILDE},
};
const char value = itr[0];
auto test_symbol = [value](const SymbolItem &item) -> bool { return item.value == value; };
const SymbolItem *symbol_itr = std::find_if(std::begin(symbols), std::end(symbols), test_symbol);
if (symbol_itr != std::end(symbols)) {
cont.append(SymbolToken{string_view_from_range(itr, itr + 1), symbol_itr->type});
itr++;
}
}
/* Match a string or char literal. */
static void eval_string_literal(std::string_view::iterator &itr,
std::string_view::iterator last,
TokenIterator &cont)
{
const char opening = itr[0];
if (!(opening == '"' || opening == '\'')) {
return;
}
const std::string_view::iterator start{itr++};
bool scape{false};
while (itr < last && !(!scape && itr[0] == opening)) {
scape = itr[0] == '\\' && !scape;
itr++;
}
if (!(itr < last)) {
itr = start;
return;
}
itr++;
cont.append(StringLiteralToken{string_view_from_range(start, itr)});
}
void TokenIterator::print_unkown_token(std::string_view filepath,
std::string_view::iterator start,
std::string_view::iterator where)
{
size_t line = 1;
while (start < where) {
if (start[0] == '\n') {
line++;
}
start++;
}
printf("%s\n", fmt::format("{}({}) Unknown token: ({})", filepath, line, where[0]).c_str());
}
void TokenIterator::skip_break_lines()
{
while (next_ < token_stream_.end() && std::holds_alternative<BreakLineToken>(*next_)) {
next_++;
}
}
void TokenIterator::process_text(std::string_view filepath, std::string_view text)
{
std::string_view::iterator itr = text.begin();
const std::string_view::iterator end = text.end();
auto eval_token = [this](std::string_view::iterator &itr,
std::string_view::iterator end,
auto &&eval_fn) -> bool {
std::string_view::iterator current = itr;
eval_fn(itr, end, *this);
return current != itr;
};
while (itr != text.end()) {
const std::string_view::iterator current = itr;
if (eval_token(itr, end, eval_space) || eval_token(itr, end, eval_line_comment) ||
eval_token(itr, end, eval_multiline_comment) || eval_token(itr, end, eval_identifier) ||
eval_token(itr, end, eval_int_literal) || eval_token(itr, end, eval_string_literal) ||
eval_token(itr, end, eval_symbol) || eval_token(itr, end, eval_break_line))
{
continue;
}
/* Unkown token found. */
if (current == itr) {
print_unkown_token(filepath, text.begin(), itr);
token_stream_.clear();
break;
}
}
next_ = token_stream_.begin();
};
TokenVariant *TokenIterator::next_variant()
{
if (next_ < token_stream_.end()) {
return next_++;
}
return nullptr;
}
bool TokenIterator::has_finish()
{
return !(next_ < token_stream_.end());
}
void TokenIterator::push_waypoint()
{
waypoints_.append(next_);
}
void TokenIterator::end_waypoint(bool success)
{
if (!success) {
if (last_unmatched < next_) {
last_unmatched = next_;
}
next_ = waypoints_.last();
}
waypoints_.remove_last();
}
KeywordToken *TokenIterator::next_keyword(KeywordType type)
{
TokenVariant *tmp = next_;
if (KeywordToken *keyword = next<KeywordToken>(); keyword && keyword->type == type) {
return keyword;
}
next_ = tmp;
return nullptr;
}
SymbolToken *TokenIterator::next_symbol(SymbolType type)
{
TokenVariant *tmp = next_;
if (SymbolToken *symbol = next<SymbolToken>(); symbol && symbol->type == type) {
return symbol;
}
next_ = tmp;
return nullptr;
}
} // namespace blender::dna::lex

View File

@ -0,0 +1,181 @@
#pragma once
#include "BLI_vector.hh"
#include <string_view>
#include <variant>
namespace blender::dna::lex {
enum class SymbolType : int8_t {
COLON = 0,
SEMICOLON,
LPAREN,
RPAREN,
LBRACKET,
RBRACKET,
LBRACE,
RBRACE,
ASSIGN,
HASH,
DOT,
COMMA,
STAR,
LESS,
GREATER,
BIT_OR,
BIT_AND,
PLUS,
MINUS,
EXCLAMATION,
PERCENT,
CARET,
QUESTION,
TILDE,
BACKSLASH,
SLASH,
};
enum class KeywordType : int8_t {
INCLUDE = 0,
STRUCT,
TYPEDEF,
CLASS,
ENUM,
DEFINE,
PUBLIC,
PRIVATE,
CONST,
VOID,
CHAR,
CHAR16_T,
CHAR32_T,
UNSIGNED,
SIGNED,
SHORT,
LONG,
ULONG,
INT,
INT8_T,
INT16_T,
INT32_T,
INT64_T,
UINT8_T,
UINT16_T,
UINT32_T,
UINT64_T,
FLOAT,
DOUBLE,
IF,
IFDEF,
IFNDEF,
ENDIF,
EXTERN,
PRAGMA,
ONCE,
DNA_DEFINE_CXX_METHODS,
DNA_DEPRECATED,
ENUM_OPERATORS,
BLI_STATIC_ASSERT_ALIGN,
DNA_DEPRECATED_ALLOW
};
struct Token {
std::string_view where;
};
struct BreakLineToken : public Token {};
struct IdentifierToken : public Token {};
struct StringLiteralToken : public Token {};
struct IntLiteralToken : public Token {
int32_t val{0};
};
struct SymbolToken : public Token {
SymbolType type;
};
struct KeywordToken : public Token {
KeywordType type;
};
using TokenVariant = std::variant<BreakLineToken,
IdentifierToken,
IntLiteralToken,
SymbolToken,
KeywordToken,
StringLiteralToken>;
struct TokenIterator {
/** Last token that fails to match a token request. */
TokenVariant *last_unmatched{nullptr};
private:
/* Token stream. */
Vector<TokenVariant> token_stream_;
/* Return points to use if the parser fails when parsing tokens. */
Vector<TokenVariant *> waypoints_;
/* Pointer to next token to use. */
TokenVariant *next_{nullptr};
/* Print line where a unkown token was found. */
void print_unkown_token(std::string_view filepath,
std::string_view::iterator start,
std::string_view::iterator where);
void skip_break_lines();
public:
/* Iterates over the input text looking for tokens. */
void process_text(std::string_view filepath, std::string_view text);
/* Add a return point in case the token parser fails create an item. */
void push_waypoint();
/**
* Removes the last return point, if `success==false` the iterator steps back to the return
* point.
*/
void end_waypoint(bool success);
/* Return the pointer to the next token, and advances the iterator. */
TokenVariant *next_variant();
/* Checks if the token iterator has reach the last token. */
bool has_finish();
/* Appends a token. */
template<class TokenType> void append(TokenType &&token)
{
token_stream_.append(token);
}
/**
* Return the next token if it type matches to `Type`.
* Break lines are skipped for not break lines requested tokens.
*/
template<class Type> Type *next()
{
TokenVariant *current_next = next_;
if constexpr (!std::is_same_v<Type, BreakLineToken>) {
skip_break_lines();
}
if (next_ < token_stream_.end() && std::holds_alternative<Type>(*next_)) {
return &std::get<Type>(*next_++);
}
if (last_unmatched < next_) {
last_unmatched = next_;
}
next_ = current_next;
return nullptr;
}
/* Return the next token if it matches to the requested keyword type. */
KeywordToken *next_keyword(KeywordType type);
/* Return the next token if it matches to the requested symbol type. */
SymbolToken *next_symbol(SymbolType type);
};
} // namespace blender::dna::lex

View File

@ -0,0 +1,858 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "dna_parser.hh"
#include <fmt/format.h>
#ifdef DEBUG_PRINT_DNA_PARSER
namespace blender::dna::parser {
void printf_struct(ast::Struct &val, size_t padding);
struct StructMemberPrinter {
size_t padding;
void operator()(ast::Variable &var) const
{
if (var.const_tag) {
printf("const ");
}
printf("%s", fmt::format("{} ", var.type).c_str());
bool first = true;
for (auto &variable_item : var.items) {
if (!first) {
printf(",");
}
first = false;
printf("%s",
fmt::format("{}{}", variable_item.ptr.value_or(""), variable_item.name).c_str());
for (auto &size : variable_item.size) {
if (std::holds_alternative<std::string_view>(size)) {
printf("%s", fmt::format("[{}]", std::get<std::string_view>(size)).c_str());
}
else {
printf("%s", fmt::format("[{}]", std::get<int32_t>(size)).c_str());
}
}
}
}
void operator()(ast::FunctionPtr &fn) const
{
if (fn.const_tag) {
printf("%s", "const ");
}
printf("%s", fmt::format("{} (*{})(...)", fn.type, fn.name).c_str());
}
void operator()(ast::PointerToArray &ptr) const
{
printf("%s", fmt::format("{} (*{})[{}]", ptr.type, ptr.name, ptr.size).c_str());
}
void operator()(ast::Struct &val) const
{
printf_struct(val, padding);
}
};
struct ParserDebugPrinter {
size_t padding;
void operator()(ast::DefineInt &val) const
{
printf("%s\n", fmt::format("#define {} {}", val.name, val.value).c_str());
}
void operator()(ast::Enum &val) const
{
printf("%s", fmt::format("enum {}", val.name.value_or("unnamed")).c_str());
if (val.type) {
printf("%s", fmt::format(": {}", val.type.value()).c_str());
}
printf(" {...};\n");
}
void operator()(ast::Struct &val) const
{
printf_struct(val, padding);
printf(";\n");
}
void operator()(ast::FunctionPtr &fn) const
{
StructMemberPrinter{padding + 1}.operator()(fn);
printf("\n");
}
void operator()(ast::Variable &var) const
{
StructMemberPrinter{padding + 1}.operator()(var);
printf("\n");
}
};
void printf_struct(ast::Struct &val, size_t padding)
{
printf("%s\n", fmt::format("struct {} {{", val.name).c_str());
for (auto &item : val.items) {
for (size_t x = 0; x < padding + 1; x++) {
printf(" ");
}
std::visit(StructMemberPrinter{padding + 1}, item);
printf(";\n");
}
for (size_t x = 0; x < padding; x++) {
printf(" ");
}
printf("}");
};
} // namespace blender::dna::parser
#endif
namespace blender::dna::parser::ast {
/**
* Parser that matches a sequence of elements to parse, fails if any `Args` in `Args...` fails to
* parse.
* Given the following example:`Sequence<HashSymbol,PragmaKeyword,OnceKeyword>` parses when the
* text contains `#pragma once`.
*/
template<class... Args> struct Sequence : public std::tuple<Args...> {
private:
template<std::size_t I, typename Type>
static inline bool parse_type(TokenIterator &token_iterator, Sequence &sequence)
{
std::optional<Type> val = Type::parse(token_iterator);
if (val.has_value()) {
std::get<I>(sequence) = std::move(val.value());
}
return val.has_value();
};
template<std::size_t... I>
static inline bool parse_impl(std::index_sequence<I...> /*indices*/,
TokenIterator &token_iterator,
Sequence &sequence)
{
return (parse_type<I, Args>(token_iterator, sequence) && ...);
};
public:
static std::optional<Sequence> parse(TokenIterator &token_iterator)
{
token_iterator.push_waypoint();
Sequence sequence;
const bool success = parse_impl(
std::make_index_sequence<sizeof...(Args)>{}, token_iterator, sequence);
token_iterator.end_waypoint(success);
if (success) {
return sequence;
}
return std::nullopt;
}
};
/**
* Parser that don't fails if `Type` can't be parsed.
* Parsing the sequence `Sequence<Optional<ConstKeyword>,IntKeyword, Identifier,SemicolonSymbol>`
* success either if text is `const int num;` or `int num;`
*/
template<typename Type> struct Optional : public std::optional<Type> {
static std::optional<Optional> parse(TokenIterator &token_iterator)
{
token_iterator.push_waypoint();
std::optional<Type> result = Type::parse(token_iterator);
token_iterator.end_waypoint(result.has_value());
return Optional{std::move(result)};
}
};
/**
* Parser that tries to match any `Arg` in `Args...`
* Parsing the sequence `Sequence<Variant<IntKeyword,FloatKeyword>,Identifier,SemicolonSymbol>`
* success either if text is `int num;` or `float num;`
*/
template<class... Args> struct Variant : public std::variant<Args...> {
private:
template<typename Type>
static inline bool parse_type(TokenIterator &token_iterator, Variant &variant)
{
token_iterator.push_waypoint();
std::optional<Type> val = Type::parse(token_iterator);
if (val.has_value()) {
variant.template emplace<Type>(std::move(val.value()));
}
token_iterator.end_waypoint(val.has_value());
return val.has_value();
};
public:
static std::optional<Variant> parse(TokenIterator &token_iterator)
{
Variant tmp;
if ((parse_type<Args>(token_iterator, tmp) || ...)) {
return tmp;
}
return std::nullopt;
}
};
/** Keyword parser. */
template<KeywordType Type> struct Keyword {
static std::optional<Keyword> parse(TokenIterator &token_iterator)
{
if (token_iterator.next_keyword(Type)) {
return Keyword{};
}
return std::nullopt;
}
};
using ConstKeyword = Keyword<KeywordType::CONST>;
using IncludeKeyword = Keyword<KeywordType::INCLUDE>;
using StructKeyword = Keyword<KeywordType::STRUCT>;
using DefineKeyword = Keyword<KeywordType::DEFINE>;
using UnsignedKeyword = Keyword<KeywordType::UNSIGNED>;
using IntKeyword = Keyword<KeywordType::INT>;
using Int8Keyword = Keyword<KeywordType::INT8_T>;
using Int16Keyword = Keyword<KeywordType::INT16_T>;
using Int32Keyword = Keyword<KeywordType::INT32_T>;
using Int64Keyword = Keyword<KeywordType::INT64_T>;
using UInt8Keyword = Keyword<KeywordType::UINT8_T>;
using UInt16Keyword = Keyword<KeywordType::UINT16_T>;
using UInt32Keyword = Keyword<KeywordType::UINT32_T>;
using UInt64Keyword = Keyword<KeywordType::UINT64_T>;
using FloatKeyword = Keyword<KeywordType::FLOAT>;
using DoubleKeyword = Keyword<KeywordType::DOUBLE>;
using ShortKeyword = Keyword<KeywordType::SHORT>;
using CharKeyword = Keyword<KeywordType::CHAR>;
using VoidKeyword = Keyword<KeywordType::VOID>;
using IfKeyword = Keyword<KeywordType::IF>;
using IfDefKeyword = Keyword<KeywordType::IFDEF>;
using IfnDefKeyword = Keyword<KeywordType::IFNDEF>;
using EndIfKeyword = Keyword<KeywordType::ENDIF>;
using ExternKeyword = Keyword<KeywordType::EXTERN>;
using TypedefKeyword = Keyword<KeywordType::TYPEDEF>;
using PragmaKeyword = Keyword<KeywordType::PRAGMA>;
using OnceKeyword = Keyword<KeywordType::ONCE>;
using EnumKeyword = Keyword<KeywordType::ENUM>;
using ClassKeyword = Keyword<KeywordType::CLASS>;
using DNADeprecatedKeyword = Keyword<KeywordType::DNA_DEPRECATED>;
using DNADeprecatedAllowKeyword = Keyword<KeywordType::DNA_DEPRECATED_ALLOW>;
/** Symbol parser. */
template<SymbolType type> struct Symbol {
static std::optional<Symbol> parse(TokenIterator &token_iterator)
{
if (token_iterator.next_symbol(type)) {
return Symbol{};
}
return std::nullopt;
}
};
using LBracketSymbol = Symbol<SymbolType::LBRACKET>;
using RBracketSymbol = Symbol<SymbolType::RBRACKET>;
using LBraceSymbol = Symbol<SymbolType::LBRACE>;
using RBraceSymbol = Symbol<SymbolType::RBRACE>;
using LParenSymbol = Symbol<SymbolType::LPAREN>;
using RParenSymbol = Symbol<SymbolType::RPAREN>;
using StarSymbol = Symbol<SymbolType::STAR>;
using SemicolonSymbol = Symbol<SymbolType::SEMICOLON>;
using ColonSymbol = Symbol<SymbolType::COLON>;
using CommaSymbol = Symbol<SymbolType::COMMA>;
using HashSymbol = Symbol<SymbolType::HASH>;
using LessSymbol = Symbol<SymbolType::LESS>;
using GreaterSymbol = Symbol<SymbolType::GREATER>;
using AssignSymbol = Symbol<SymbolType::ASSIGN>;
using MinusSymbol = Symbol<SymbolType::MINUS>;
static void skip_until_match_paired_symbols(SymbolType left,
SymbolType right,
TokenIterator &token_iterator);
/**
* Parses a macro call, `MacroCall<KeywordType::DNA_DEFINE_CXX_METHODS>` parses
* `DNA_DEFINE_CXX_METHODS(...)`.
*/
template<lex::KeywordType Type> struct MacroCall {
static std::optional<MacroCall> parse(TokenIterator &token_iterator)
{
if (Sequence<Keyword<Type>, LParenSymbol>::parse(token_iterator).has_value()) {
skip_until_match_paired_symbols(SymbolType::LPAREN, SymbolType::RPAREN, token_iterator);
SemicolonSymbol::parse(token_iterator);
return MacroCall{};
}
return std::nullopt;
}
};
/** Parses a string literal. */
struct StringLiteral {
std::string_view value;
static std::optional<StringLiteral> parse(TokenIterator &token_iterator)
{
if (StringLiteralToken *literal = token_iterator.next<StringLiteralToken>(); literal) {
return StringLiteral{literal->where};
}
return std::nullopt;
}
};
/** Parses a int literal. */
struct IntLiteral {
int value;
static std::optional<IntLiteral> parse(TokenIterator &token_iterator)
{
if (IntLiteralToken *value = token_iterator.next<IntLiteralToken>(); value) {
return IntLiteral{value->val};
}
return std::nullopt;
}
};
/** Parses a identifier. */
struct Identifier {
std::string_view str;
static std::optional<Identifier> parse(TokenIterator &token_iterator)
{
if (IdentifierToken *identifier = token_iterator.next<IdentifierToken>(); identifier) {
return Identifier{identifier->where};
}
return std::nullopt;
}
};
/** Parses a include, either `#include "include_name.hh"` or `#include <path/to/include.hh>`. */
struct Include {
static std::optional<Include> parse(TokenIterator &token_iterator)
{
if (Sequence<HashSymbol, IncludeKeyword>::parse(token_iterator).has_value()) {
TokenVariant *token = token_iterator.next_variant();
while (token && !std::holds_alternative<BreakLineToken>(*token)) {
token = token_iterator.next_variant();
}
return Include{};
}
return std::nullopt;
}
};
/** Check if a token is a symbol and has a type. */
static bool inline is_symbol_type(const TokenVariant &token, const SymbolType type)
{
return std::holds_alternative<SymbolToken>(token) && std::get<SymbolToken>(token).type == type;
}
/** Parses `#define` directives except to const int defines. */
struct Define {
static std::optional<Define> parse(TokenIterator &token_iterator)
{
if (!Sequence<HashSymbol, DefineKeyword>::parse(token_iterator)) {
return std::nullopt;
}
bool scape_bl = false;
for (TokenVariant *token = token_iterator.next_variant(); token;
token = token_iterator.next_variant())
{
if (std::holds_alternative<BreakLineToken>(*token) && !scape_bl) {
break;
}
scape_bl = is_symbol_type(*token, SymbolType::BACKSLASH);
}
return Define{};
}
};
/** Parses const int defines, like `#define FILE_MAX 1024`. */
std::optional<DefineInt> DefineInt::parse(TokenIterator &token_iterator)
{
using DefineConstIntSeq = Sequence<HashSymbol, DefineKeyword, Identifier, IntLiteral>;
std::optional<DefineConstIntSeq> val = DefineConstIntSeq::parse(token_iterator);
if (!val.has_value() || !token_iterator.next<BreakLineToken>()) {
return std::nullopt;
}
return DefineInt{std::get<2>(val.value()).str, std::get<3>(val.value()).value};
}
bool DefineInt::operator==(const DefineInt &other) const
{
return name == other.name && value == other.value;
}
/** Parses most c++ primitive types. */
struct PrimitiveType {
std::string_view str;
static std::optional<PrimitiveType> parse(TokenIterator &token_iterator)
{
/* TODO: Add all primitive types. */
using PrimitiveTypeVariants = Variant<IntKeyword,
CharKeyword,
ShortKeyword,
FloatKeyword,
DoubleKeyword,
VoidKeyword,
Sequence<UnsignedKeyword, IntKeyword>,
Sequence<UnsignedKeyword, ShortKeyword>,
Sequence<UnsignedKeyword, CharKeyword>,
Int8Keyword,
Int16Keyword,
Int32Keyword,
Int64Keyword,
UInt8Keyword,
UInt16Keyword,
UInt32Keyword,
UInt64Keyword,
Keyword<KeywordType::LONG>,
Keyword<KeywordType::ULONG>>;
std::optional<PrimitiveTypeVariants> type = PrimitiveTypeVariants::parse(token_iterator);
if (!type.has_value()) {
return std::nullopt;
}
/* Use `unsigned int` as uint32?.... */
using namespace std::string_view_literals;
/* Note: makesdna ignores `unsigned` keyword. */
static constexpr std::string_view primitive_types[]{
"int"sv, "char"sv, "short"sv, "float"sv, "double"sv,
"void"sv, "int"sv, "short"sv, "char"sv, "int8_t"sv,
"int16_t"sv, "int32_t"sv, "int64_t"sv, "uint8_t"sv, "uint16_t"sv,
"uint32_t"sv, "uint64_t"sv, "long"sv, "ulong"sv,
};
return PrimitiveType{primitive_types[type.value().index()]};
}
};
/**
* Parses the type in variable declarations or function return value, either a primitive type or
* custom type.
*/
struct Type {
bool const_tag{false};
std::string_view str;
static std::optional<Type> parse(TokenIterator &token_iterator)
{
using TypeVariant = Variant<PrimitiveType, Sequence<Optional<StructKeyword>, Identifier>>;
using TypeSequence = Sequence<Optional<ConstKeyword>, TypeVariant>;
const std::optional<TypeSequence> type_seq = TypeSequence::parse(token_iterator);
if (!type_seq) {
return std::nullopt;
}
const bool const_tag = std::get<0>(type_seq.value()).has_value();
const TypeVariant &type_variant = std::get<1>(type_seq.value());
if (std::holds_alternative<PrimitiveType>(type_variant)) {
return Type{const_tag, std::get<0>(type_variant).str};
}
return Type{const_tag, std::get<1>(std::get<1>(type_variant)).str};
}
};
/** Parses the array part of variable declarations: with `int num[3][4];` parses `[3][4]`. */
static Vector<std::variant<std::string_view, int32_t>> variable_size_array_part(
TokenIterator &token_iterator)
{
Vector<std::variant<std::string_view, int32_t>> result;
/* Dynamic array. */
if (Sequence<LBracketSymbol, RBracketSymbol>::parse(token_iterator).has_value()) {
result.append(std::string_view{""});
}
while (true) {
using ArraySize = Sequence<LBracketSymbol, Variant<IntLiteral, Identifier>, RBracketSymbol>;
const std::optional<ArraySize> size_seq = ArraySize::parse(token_iterator);
if (!size_seq.has_value()) {
break;
}
const auto &item_size = std::get<1>(size_seq.value());
if (std::holds_alternative<IntLiteral>(item_size)) {
result.append(std::get<IntLiteral>(item_size).value);
}
else {
result.append(std::get<Identifier>(item_size).str);
}
}
return result;
}
/**
* Variable parser, parses multiple inline declarations, like:
* `int value;`
* `const int value[256][DEFINE_VALUE];`
* `float *value1,value2[256][256];`
*/
std::optional<Variable> Variable::parse(TokenIterator &token_iterator)
{
const std::optional<Type> type{Type::parse(token_iterator)};
if (!type) {
return std::nullopt;
}
Variable variable;
variable.const_tag = type.value().const_tag;
variable.type = type.value().str;
while (true) {
std::string start;
for (; StarSymbol::parse(token_iterator);) {
start += '*';
}
std::optional<Identifier> name{Identifier::parse(token_iterator)};
if (!name.has_value()) {
return std::nullopt;
}
Variable::Item item{};
item.ptr = !start.empty() ? std::optional{start} : std::nullopt;
item.name = name.value().str;
item.size = variable_size_array_part(token_iterator);
variable.items.append(std::move(item));
DNADeprecatedKeyword::parse(token_iterator);
if (SemicolonSymbol::parse(token_iterator).has_value()) {
break;
}
if (!CommaSymbol::parse(token_iterator).has_value()) {
return std::nullopt;
}
}
return variable;
}
bool Variable::Item::operator==(const Variable::Item &other) const
{
return ptr == other.ptr && name == other.name && size == other.size;
}
bool Variable::operator==(const Variable &other) const
{
return type == other.type && items == other.items;
}
/* Skips tokens until match the closing right symbol, like function body braces `{...}`. */
static void skip_until_match_paired_symbols(SymbolType left,
SymbolType right,
TokenIterator &token_iterator)
{
int left_count = 1;
for (TokenVariant *token = token_iterator.next_variant(); token;
token = token_iterator.next_variant())
{
if (is_symbol_type(*token, right)) {
left_count--;
if (left_count == 0) {
break;
}
}
else if (is_symbol_type(*token, left)) {
left_count++;
}
}
};
/**
* Parses function pointer variables, like `bool (*poll)(struct bContext *);`
*/
std::optional<FunctionPtr> FunctionPtr::parse(TokenIterator &token_iterator)
{
using FunctionPtrBegin = Sequence<Type,
Optional<StarSymbol>,
LParenSymbol,
StarSymbol,
Identifier,
RParenSymbol,
LParenSymbol>;
const std::optional<FunctionPtrBegin> fn = FunctionPtrBegin::parse(token_iterator);
if (!fn.has_value()) {
return std::nullopt;
}
FunctionPtr fn_ptr{};
fn_ptr.const_tag = std::get<0>(fn.value()).const_tag;
fn_ptr.type = std::get<0>(fn.value()).str;
fn_ptr.name = std::get<4>(fn.value()).str;
/* Skip Function params. */
skip_until_match_paired_symbols(SymbolType::LPAREN, SymbolType::RPAREN, token_iterator);
/* Closing sequence. */
if (!SemicolonSymbol::parse(token_iterator).has_value()) {
return std::nullopt;
}
return fn_ptr;
}
bool FunctionPtr::operator==(const FunctionPtr &other) const
{
return type == other.type && name == other.name;
}
bool PointerToArray::operator==(const PointerToArray &other) const
{
return type == other.type && name == other.name && size == other.size;
}
/**
* Parses array pointer variables, like `float (*vert_coords_prev)[3];`
*/
std::optional<PointerToArray> PointerToArray::parse(TokenIterator &token_iterator)
{
using PointerToArraySequence = Sequence<Type,
LParenSymbol,
StarSymbol,
Identifier,
RParenSymbol,
LBracketSymbol,
IntLiteral,
RBracketSymbol,
SemicolonSymbol>;
std::optional<PointerToArraySequence> val = PointerToArraySequence::parse(token_iterator);
if (!val.has_value()) {
return std::nullopt;
}
PointerToArray ptr{};
ptr.type = std::get<0>(val.value()).str;
ptr.name = std::get<3>(val.value()).str;
ptr.size = std::get<6>(val.value()).value;
return ptr;
}
template<KeywordType... type> static bool is_keyword_type(TokenVariant token)
{
return std::holds_alternative<KeywordToken>(token) &&
((std::get<KeywordToken>(token).type == type) || ...);
}
/**
* Parses `#if....#endif` code blocks.
*/
struct IfDef {
static std::optional<IfDef> parse(TokenIterator &token_iterator)
{
using IfDefBeginSequence =
Sequence<HashSymbol, Variant<IfDefKeyword, IfKeyword, IfnDefKeyword>>;
const std::optional<IfDefBeginSequence> val = IfDefBeginSequence::parse(token_iterator);
if (!val.has_value()) {
return std::nullopt;
};
int ifdef_deep = 1;
bool hash_carried = false;
for (TokenVariant *token = token_iterator.next_variant(); token;
token = token_iterator.next_variant())
{
if (hash_carried &&
is_keyword_type<KeywordType::IF, KeywordType::IFDEF, KeywordType::IFNDEF>(*token))
{
ifdef_deep++;
}
if (hash_carried && is_keyword_type<KeywordType::ENDIF>(*token)) {
ifdef_deep--;
}
if (ifdef_deep == 0) {
break;
}
hash_carried = is_symbol_type(*token, SymbolType::HASH);
}
/* Not matching #endif. */
if (ifdef_deep != 0) {
return std::nullopt;
}
return IfDef{};
}
};
/**
* Parses struct declarations.
*/
std::optional<Struct> Struct::parse(TokenIterator &token_iterator)
{
using StructBeginSequence =
Sequence<Optional<TypedefKeyword>, StructKeyword, Optional<Identifier>, LBraceSymbol>;
std::optional<StructBeginSequence> struct_seq = StructBeginSequence::parse(token_iterator);
if (!struct_seq.has_value()) {
return std::nullopt;
}
Struct result{};
if (std::get<2>(struct_seq.value()).has_value()) {
result.name = std::get<2>(struct_seq.value()).value().str;
}
while (true) {
using DNA_DEF_CCX_Macro = MacroCall<lex::KeywordType::DNA_DEFINE_CXX_METHODS>;
if (auto member = Variant<Variable, FunctionPtr, PointerToArray, Struct>::parse(
token_iterator);
member.has_value())
{
result.items.append(std::move(member.value()));
}
else if (DNA_DEF_CCX_Macro::parse(token_iterator).has_value() ||
IfDef::parse(token_iterator).has_value())
{
}
else {
break;
}
}
using StructEndSequence = Sequence<RBraceSymbol, Optional<Identifier>, SemicolonSymbol>;
std::optional<StructEndSequence> struct_end = StructEndSequence ::parse(token_iterator);
if (!struct_end.has_value()) {
return std::nullopt;
}
if (std::get<1>(struct_end.value()).has_value()) {
result.member_name = std::get<1>(struct_end.value()).value().str;
}
if (result.member_name == result.name && result.name.empty()) {
return std::nullopt;
}
return result;
}
bool Struct::operator==(const Struct &other) const
{
return name == other.name && items == other.items;
}
/** Parses non used definitions that DNA. */
struct Skip {
static std::optional<Skip> parse(TokenIterator &token_iterator)
{
using UnusedDeclarations =
Variant<Define,
Include,
IfDef,
Sequence<HashSymbol, PragmaKeyword, OnceKeyword>,
Sequence<HashSymbol, HashSymbol, Struct>,
Sequence<ExternKeyword, Variable>,
MacroCall<lex::KeywordType::BLI_STATIC_ASSERT_ALIGN>,
MacroCall<lex::KeywordType::ENUM_OPERATORS>,
Sequence<TypedefKeyword, StructKeyword, Identifier, Identifier, SemicolonSymbol>>;
if (UnusedDeclarations::parse(token_iterator).has_value()) {
return Skip{};
}
/* Forward declare. */
if (Sequence<StructKeyword, Identifier>::parse(token_iterator).has_value()) {
for (; Sequence<CommaSymbol, Identifier>::parse(token_iterator).has_value();) {
}
if (SemicolonSymbol::parse(token_iterator).has_value()) {
return Skip{};
}
}
else if (token_iterator.next<BreakLineToken>()) {
return Skip{};
}
return std::nullopt;
}
};
/** Parse enums, with a name or not and with a fixed type or not. */
std::optional<Enum> ast::Enum::parse(TokenIterator &token_iterator)
{
using EnumBeginSequence = Sequence<Optional<TypedefKeyword>,
EnumKeyword,
Optional<ClassKeyword>,
Optional<Identifier>,
Optional<Sequence<ColonSymbol, PrimitiveType>>,
LBraceSymbol>;
std::optional<EnumBeginSequence> enum_begin = EnumBeginSequence::parse(token_iterator);
if (!enum_begin.has_value()) {
return std::nullopt;
}
Enum enum_def;
if (std::get<3>(enum_begin.value()).has_value()) {
enum_def.name = std::get<3>(enum_begin.value()).value().str;
}
if (std::get<4>(enum_begin.value()).has_value()) {
enum_def.type = std::get<1>(std::get<4>(enum_begin.value()).value()).str;
}
/* Skip enum body. */
skip_until_match_paired_symbols(SymbolType::LBRACE, SymbolType::RBRACE, token_iterator);
/* Enum end sequence. */
if (!Sequence<Optional<Identifier>, Optional<DNADeprecatedKeyword>, SemicolonSymbol>::parse(
token_iterator)
.has_value())
{
return std::nullopt;
}
return enum_def;
}
bool Enum::operator==(const Enum &other) const
{
return name == other.name && type == other.type;
}
} // namespace blender::dna::parser::ast
namespace blender::dna::parser {
static void print_unhandled_token_error(std::string_view filepath,
std::string_view text,
lex::TokenVariant *what)
{
auto visit_fn = [text, filepath](auto &&token) {
std::string_view::iterator itr = text.begin();
size_t line = 1;
while (itr < token.where.begin()) {
if (itr[0] == '\n') {
line++;
}
itr++;
}
printf("%s\n",
fmt::format("{}{} Unhandled token: \"{}\"", filepath, line, token.where).c_str());
};
std::visit(visit_fn, *what);
}
bool parse_include(std::string_view filepath,
std::string_view text,
lex::TokenIterator &token_iterator,
Vector<ast::CppType> &dest)
{
using namespace ast;
int dna_deprecated_allow_count = 0;
using DNADeprecatedAllowSeq = Sequence<HashSymbol, IfDefKeyword, DNADeprecatedAllowKeyword>;
using EndIfSeq = Sequence<HashSymbol, EndIfKeyword>;
while (!token_iterator.has_finish()) {
using CPPTypeVariant = Variant<Struct,
Enum,
Sequence<Optional<TypedefKeyword>, FunctionPtr>,
Variable,
DefineInt,
DNADeprecatedAllowSeq,
EndIfSeq,
Skip>;
std::optional<CPPTypeVariant> val = CPPTypeVariant::parse(token_iterator);
if (!val.has_value()) {
print_unhandled_token_error(filepath, text, token_iterator.last_unmatched);
return false;
}
if (std::holds_alternative<Struct>(val.value())) {
dest.append(std::move(std::get<Struct>(val.value())));
}
else if (std::holds_alternative<DefineInt>(val.value())) {
dest.append(std::move(std::get<DefineInt>(val.value())));
}
else if (std::holds_alternative<Variable>(val.value())) {
continue;
dest.append(std::move(std::get<Variable>(val.value())));
}
else if (std::holds_alternative<Enum>(val.value())) {
Enum &enum_def = std::get<Enum>(val.value());
/** Keep only named enums with fixed type. */
if (!enum_def.name.has_value() || !enum_def.type.has_value()) {
continue;
}
dest.append(enum_def);
}
else if (std::holds_alternative<DNADeprecatedAllowSeq>(val.value())) {
dna_deprecated_allow_count++;
}
else if (std::holds_alternative<EndIfSeq>(val.value())) {
dna_deprecated_allow_count++;
if (dna_deprecated_allow_count < 0) {
return false;
}
}
}
#ifdef DEBUG_PRINT_DNA_PARSER
static constexpr std::string_view debug_file{""};
if (!debug_file.empty() && filepath.find(debug_file) != filepath.npos) {
for (auto &val : dest) {
std::visit(ParserDebugPrinter{}, val);
}
}
#endif
return true;
}
} // namespace blender::dna::parser

View File

@ -0,0 +1,101 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "BLI_vector.hh"
#include "dna_lexer.hh"
#include <optional>
#include <string>
#include <string_view>
#include <variant>
namespace blender::dna::parser {
namespace ast {
using namespace lex;
/* Constant int defined value. */
struct DefineInt {
std::string_view name;
int32_t value{0};
static std::optional<DefineInt> parse(TokenIterator &token_iterator);
bool operator==(const DefineInt &other) const;
};
/**
* Variable declaration, can hold multiple inline declarations, like:
* `float *value1,value2[256][256];`
*/
struct Variable {
struct Item {
std::optional<std::string> ptr;
std::string_view name;
/** Item array size definition, empty for not arrays items. */
Vector<std::variant<std::string_view, int32_t>> size;
bool operator==(const Item &other) const;
};
bool const_tag{false};
std::string_view type;
Vector<Item> items;
bool operator==(const Variable &other) const;
static std::optional<Variable> parse(TokenIterator &token_iterator);
};
/* Function pointer declaration. */
struct FunctionPtr {
bool const_tag{false};
std::string_view type;
std::string_view name;
bool operator==(const FunctionPtr &other) const;
static std::optional<FunctionPtr> parse(TokenIterator &token_iterator);
};
/* Pointer to array declaration. */
struct PointerToArray {
std::string_view type;
std::string_view name;
int32_t size;
bool operator==(const PointerToArray &other) const;
static std::optional<PointerToArray> parse(TokenIterator &token_iterator);
};
/* Struct declaration.*/
struct Struct {
std::string_view name;
/* Recursive struct keep inline buffer capacity to 0. */
Vector<std::variant<Variable, FunctionPtr, PointerToArray, Struct>, 0> items;
/* Name set if struct is declared as member variable. */
std::string_view member_name;
static std::optional<Struct> parse(TokenIterator &token_iterator);
bool operator==(const Struct &other) const;
};
/* Enum declaration. */
struct Enum {
/* Enum name, unset for unnamed enums. */
std::optional<std::string_view> name;
/** Fixed type specification. */
std::optional<std::string_view> type;
bool operator==(const Enum &other) const;
static std::optional<Enum> parse(TokenIterator &token_iterator);
};
using CppType = std::variant<DefineInt, Enum, Struct, FunctionPtr, Variable>;
} // namespace ast
bool parse_include(std::string_view filepath,
std::string_view text,
lex::TokenIterator &token_iterator,
Vector<ast::CppType> &c);
} // namespace blender::dna::parser

View File

@ -0,0 +1,103 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "testing/testing.h"
#include "dna_parser.hh"
namespace blender::dna::parser::tests {
TEST(parser, parse_file)
{
std::string_view text = R"x(
/*----------------------------------------------------------------*/
/** #pragma once and includes */
#pragma once
#include "...."
#include "DNA_ID.h"
#include "DNA_armature_types.h"
#include "DNA_listBase.h"
#include "DNA_session_uid_types.h"
#include "DNA_userdef_types.h"
#include "DNA_vec_types.h"
// Forward declarations
struct Collection;
struct GHash;
struct Object, SpaceLink;
#if 0
typedef enum class UnusedEnum: uint8_t {
/* vert is selected */
UNUSED_1 = (1 + 0*FILE_MAX),
UNUSED_2 = (1 << 1),
} UnusedEnum;
ENUM_OPERATORS(UnusedEnum, UNUSED_2);
#endif
/* Const int define */
#define FILE_MAX 1024
/* define non const int*/
#define DNA_DEFINE_CXX_METHODS() ....
/** bMotionPathVert, taken from DNA_action_Types.h
* modified with a child struct. */
typedef struct bMotionPathVert {
DNA_DEFINE_CXX_METHODS(bMotionPathVert);
/* Child struct */
struct bMotionPathVertItem {
DNA_DEFINE_CXX_METHODS(bMotionPathVert);
float co[3], pre[235];
struct Link *next, *pre;
char path[FILE_MAX];
bool (*poll)(bContext *, ARegion *);
float (*data_src)[256];
};
/** Coordinates of point in 3D-space. */
float co[3];
/** Quick settings. */
int flag;
} bMotionPathVert;
/** eMotionPathVert_Flag, taken from DNA_action_Types.h
* modified with a fixed size. */
typedef enum class eMotionPathVert_Flag : uint8_t {
/* vert is selected */
VERT_SEL = (1 << 0),
VERT_KEY = (1 << 1),
} eMotionPathVert_Flag;
ENUM_OPERATORS(eMotionPathVert_Flag, VERT_KEY);
)x";
using namespace ast;
lex::TokenIterator iterator;
iterator.process_text("", text);
blender::Vector<CppType> cpp_defines;
const bool parse_result = parse_include("", text, iterator, cpp_defines);
Vector<CppType> expected{
{DefineInt{"FILE_MAX", 1024}},
{
Struct{"bMotionPathVert",
{Struct{"bMotionPathVertItem",
{
{Variable{false, "float", {{{}, "co", {{3}}}, {{}, "pre", {{235}}}}}},
{Variable{false, "Link", {{"*", "next", {}}, {"*", "pre", {}}}}},
{Variable{false, "char", {{{}, "path", {{"FILE_MAX"}}}}}},
{FunctionPtr{false, "bool", "poll"}},
{PointerToArray{"float", "data_src", 256}},
}},
{Variable{false, "float", {{{}, "co", {{3}}}}}},
{Variable{false, "int", {{{}, "flag", {}}}}}}},
},
Enum{"eMotionPathVert_Flag", "uint8_t"}};
ASSERT_TRUE(parse_result);
ASSERT_EQ(expected, cpp_defines);
}
} // namespace blender::dna::parser::tests

View File

@ -31,6 +31,11 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fmt/format.h>
#include <fstream>
#include <iostream>
#include <optional>
#include <sstream>
#include "MEM_guardedalloc.h"
@ -42,6 +47,7 @@
#include "BLI_system.h" /* for 'BLI_system_backtrace' stub. */
#include "BLI_utildefines.h"
#include "dna_parser.hh"
#include "dna_utils.h"
#define SDNA_MAX_FILENAME_LENGTH 255
@ -148,12 +154,6 @@ static int add_name(const char *str);
*/
static short *add_struct(int namecode);
/**
* Remove comments from this buffer. Assumes that the buffer refers to
* ascii-code text.
*/
static int preprocess_include(char *maindata, const int maindata_len);
/**
* Scan this file for serializable types.
*/
@ -560,105 +560,6 @@ static char *match_preproc_strstr(char *__restrict str, const char *__restrict s
return nullptr;
}
static int preprocess_include(char *maindata, const int maindata_len)
{
/* NOTE: len + 1, last character is a dummy to prevent
* comparisons using uninitialized memory */
char *temp = static_cast<char *>(MEM_mallocN(maindata_len + 1, "preprocess_include"));
temp[maindata_len] = ' ';
memcpy(temp, maindata, maindata_len);
/* remove all c++ comments */
/* replace all enters/tabs/etc with spaces */
char *cp = temp;
int a = maindata_len;
int comment = 0;
while (a--) {
if (cp[0] == '/' && cp[1] == '/') {
comment = 1;
}
else if (*cp == '\n') {
comment = 0;
}
if (comment || *cp < 32 || *cp > 128) {
*cp = 32;
}
cp++;
}
/* No need for leading '#' character. */
const char *cpp_block_start = "ifdef __cplusplus";
const char *cpp_block_end = "endif";
/* data from temp copy to maindata, remove comments and double spaces */
cp = temp;
char *md = maindata;
int newlen = 0;
comment = 0;
a = maindata_len;
bool skip_until_closing_brace = false;
while (a--) {
if (cp[0] == '/' && cp[1] == '*') {
comment = 1;
cp[0] = cp[1] = 32;
}
if (cp[0] == '*' && cp[1] == '/') {
comment = 0;
cp[0] = cp[1] = 32;
}
/* do not copy when: */
if (comment) {
/* pass */
}
else if (cp[0] == ' ' && cp[1] == ' ') {
/* pass */
}
else if (cp[-1] == '*' && cp[0] == ' ') {
/* pointers with a space */
} /* skip special keywords */
else if (match_identifier(cp, "DNA_DEPRECATED")) {
/* single values are skipped already, so decrement 1 less */
a -= 13;
cp += 13;
}
else if (match_identifier(cp, "DNA_DEFINE_CXX_METHODS")) {
/* single values are skipped already, so decrement 1 less */
a -= 21;
cp += 21;
skip_until_closing_brace = true;
}
else if (skip_until_closing_brace) {
if (cp[0] == ')') {
skip_until_closing_brace = false;
}
}
else if (match_preproc_prefix(cp, cpp_block_start)) {
char *end_ptr = match_preproc_strstr(cp, cpp_block_end);
if (end_ptr == nullptr) {
fprintf(stderr, "Error: '%s' block must end with '%s'\n", cpp_block_start, cpp_block_end);
}
else {
const int skip_offset = end_ptr - cp + strlen(cpp_block_end);
a -= skip_offset;
cp += skip_offset;
}
}
else {
md[0] = cp[0];
md++;
newlen++;
}
cp++;
}
MEM_freeN(temp);
return newlen;
}
static void *read_file_data(const char *filepath, int *r_len)
{
#ifdef WIN32
@ -700,198 +601,148 @@ static void *read_file_data(const char *filepath, int *r_len)
return data;
}
using namespace blender::dna::parser::ast;
struct StrucMemberRegister {
int strct{0};
short *structpoin{nullptr};
short *sp{nullptr};
const char *filepath{nullptr};
std::optional<int> _add_type(std::string_view type)
{
/** TODO: if type is enum, replace by it fixed size. */
const std::string type_str = fmt::format("{}", type);
if (ELEM(type, "long", "ulong")) {
fprintf(stderr,
"File '%s' contains use of \"%s\" in DNA struct which is not allowed\n",
filepath,
type_str.c_str());
return -1;
}
const int type_result = add_type(type_str.c_str(), 0);
if (type_result == -1) {
fprintf(
stderr, "File '%s' contains struct we can't parse \"%s\"\n", filepath, type_str.c_str());
return std::nullopt;
}
return type_result;
}
bool _add_name(int type, const std::string &name)
{
const int name_result = add_name(version_elem_static_from_alias(strct, name.c_str()));
if (name_result == -1) {
fprintf(stderr,
"File '%s' contains struct with name that can't be added \"%s\"\n",
filepath,
name.c_str());
return false;
}
sp[0] = type;
sp[1] = name_result;
structpoin[1]++;
sp += 2;
return true;
}
bool operator()(FunctionPtr &fn_ptr)
{
std::optional<int> type = _add_type(fn_ptr.type);
if (!type.has_value()) {
return false;
}
const std::string name = fmt::format("(*{})()", fn_ptr.name);
return _add_name(type.value(), name);
}
bool operator()(PointerToArray &array_ptr)
{
std::optional<int> type = _add_type(array_ptr.type);
if (!type.has_value()) {
return false;
}
const std::string name = fmt::format("(*{})[{}]", array_ptr.name, array_ptr.size);
return _add_name(type.value(), name);
}
bool operator()(Struct & /*struct*/)
{
printf("Unexpedted child struct declaration.");
return false;
}
bool operator()(Variable &var)
{
std::optional<int> type = _add_type(var.type);
if (!type.has_value()) {
return false;
}
for (auto &var_item : var.items) {
std::string name_str = fmt::format("{}{}", var_item.ptr.value_or(""), var_item.name);
for (auto &size : var_item.size) {
if (std::holds_alternative<std::string_view>(size)) {
/** TODO: Look to #defines to find name. */
// name_str += fmt::format("[{}]", std::get<std::string_view>(size));
return false;
}
else {
name_str += fmt::format("[{}]", std::get<int32_t>(size));
}
}
if (!_add_name(type.value(), name_str)) {
return false;
}
}
return true;
}
};
static int convert_include(const char *filepath)
{
/* read include file, skip structs with a '#' before it.
* store all data in temporal arrays.
*/
int maindata_len;
char *maindata = static_cast<char *>(read_file_data(filepath, &maindata_len));
char *md = maindata;
if (maindata_len == -1) {
std::ifstream file(std::string{filepath});
if (!file.is_open()) {
fprintf(stderr, "Can't read file %s\n", filepath);
return 1;
}
maindata_len = preprocess_include(maindata, maindata_len);
char *mainend = maindata + maindata_len - 1;
std::stringstream buffer;
buffer << file.rdbuf();
std::string text = buffer.str();
/* we look for '{' and then back to 'struct' */
int count = 0;
bool skip_struct = false;
while (count < maindata_len) {
/* Generate tokens. */
blender::dna::lex::TokenIterator iterator;
iterator.process_text(filepath, text);
/* Parse tokens. */
blender::Vector<blender::dna::parser::ast::CppType> cpp_defines;
blender::dna::parser::parse_include(filepath, text, iterator, cpp_defines);
/* code for skipping a struct: two hashes on 2 lines. (preprocess added a space) */
if (md[0] == '#' && md[1] == ' ' && md[2] == '#') {
skip_struct = true;
/* Generate DNA. */
for (auto &cpp_type : cpp_defines) {
if (!std::holds_alternative<Struct>(cpp_type)) {
continue;
}
if (md[0] == '{') {
md[0] = 0;
if (skip_struct) {
skip_struct = false;
}
else {
if (md[-1] == ' ') {
md[-1] = 0;
}
char *md1 = md - 2;
while (*md1 != 32) {
/* to beginning of word */
md1--;
}
md1++;
/* we've got a struct name when... */
if (match_identifier(md1 - 7, "struct")) {
const int strct = add_type(md1, 0);
if (strct == -1) {
fprintf(stderr, "File '%s' contains struct we can't parse \"%s\"\n", filepath, md1);
return 1;
}
short *structpoin = add_struct(strct);
short *sp = structpoin + 2;
DEBUG_PRINTF(1, "\t|\t|-- detected struct %s\n", types[strct]);
/* first lets make it all nice strings */
md1 = md + 1;
while (*md1 != '}') {
if (md1 > mainend) {
break;
}
if (ELEM(*md1, ',', ' ')) {
*md1 = 0;
}
md1++;
}
/* read types and names until first character that is not '}' */
md1 = md + 1;
while (*md1 != '}') {
if (md1 > mainend) {
break;
}
/* skip when it says 'struct' or 'unsigned' or 'const' */
if (*md1) {
const char *md1_prev = md1;
while (match_identifier_and_advance(&md1, "struct") ||
match_identifier_and_advance(&md1, "unsigned") ||
match_identifier_and_advance(&md1, "const"))
{
if (UNLIKELY(!ELEM(*md1, '\0', ' '))) {
/* This will happen with: `unsigned(*value)[3]` which isn't supported. */
fprintf(stderr,
"File '%s' contains non white space character "
"\"%c\" after identifier \"%s\"\n",
filepath,
*md1,
md1_prev);
return 1;
}
/* Skip ' ' or '\0'. */
md1++;
}
/* we've got a type! */
if (STR_ELEM(md1, "long", "ulong")) {
/* Forbid using long/ulong because those can be either 32 or 64 bit. */
fprintf(stderr,
"File '%s' contains use of \"%s\" in DNA struct which is not allowed\n",
filepath,
md1);
return -1;
}
const int type = add_type(md1, 0);
if (type == -1) {
fprintf(
stderr, "File '%s' contains struct we can't parse \"%s\"\n", filepath, md1);
return 1;
}
DEBUG_PRINTF(1, "\t|\t|\tfound type %s (", md1);
md1 += strlen(md1);
/* read until ';' */
while (*md1 != ';') {
if (md1 > mainend) {
break;
}
if (*md1) {
/* We've got a name. slen needs
* correction for function
* pointers! */
int slen = int(strlen(md1));
if (md1[slen - 1] == ';') {
md1[slen - 1] = 0;
const int name = add_name(version_elem_static_from_alias(strct, md1));
if (name == -1) {
fprintf(stderr,
"File '%s' contains struct with name that can't be added \"%s\"\n",
filepath,
md1);
return 1;
}
slen += additional_slen_offset;
sp[0] = type;
sp[1] = name;
if (names[name] != nullptr) {
DEBUG_PRINTF(1, "%s |", names[name]);
}
structpoin[1]++;
sp += 2;
md1 += slen;
break;
}
const int name = add_name(version_elem_static_from_alias(strct, md1));
if (name == -1) {
fprintf(stderr,
"File '%s' contains struct with name that can't be added \"%s\"\n",
filepath,
md1);
return 1;
}
slen += additional_slen_offset;
sp[0] = type;
sp[1] = name;
if (names[name] != nullptr) {
DEBUG_PRINTF(1, "%s ||", names[name]);
}
structpoin[1]++;
sp += 2;
md1 += slen;
}
md1++;
}
DEBUG_PRINTF(1, ")\n");
}
md1++;
}
}
Struct &struct_def = std::get<Struct>(cpp_type);
const std::string struct_name = fmt::format("{}", struct_def.name);
const int strct = add_type(struct_name.c_str(), 0);
if (strct == -1) {
fprintf(stderr,
"File '%s' contains struct we can't parse \"%s\"\n",
filepath,
struct_name.c_str());
return 1;
}
short *structpoin = add_struct(strct);
StrucMemberRegister member_register{strct, structpoin, structpoin + 2, filepath};
/** Register struct members. */
for (auto &item : struct_def.items) {
if (!std::visit(member_register, item)) {
return 1;
}
}
count++;
md++;
}
MEM_freeN(maindata);
return 0;
}
@ -1328,6 +1179,10 @@ static int make_structDNA(const char *base_directory,
char str[SDNA_MAX_FILENAME_LENGTH];
SNPRINTF(str, "%s%s", base_directory, includefiles[i]);
DEBUG_PRINTF(0, "\t|-- Converting %s\n", str);
/* `DNA_genfile.h` only contains functions declarations that can't be parsed at the moment. */
if (ELEM(includefiles[i], std::string_view{"DNA_defs.h"}, std::string_view{"DNA_genfile.h"})) {
continue;
}
if (convert_include(str)) {
return 1;
}