Я работаю над проектом компилятора, требующим две структуры Lexer и Parser соответственно. Предполагается, что мой парсер сможет управлять нашей структурой Lexer и вызывать ее функции-члены, когда это необходимо для таких вещей, как получение токенов для правильного анализа операторов.
Я написал два отдельных файла .h
, которые содержат предварительные объявления для всех структур, однако я вижу кучу ошибок incomplete type "(insert struct name) is not allowed"
. Я считаю, что с моим предварительным заявлением что-то не так, чего я не понимаю.
Кроме того, в моей структуре Parser в parser.cpp
есть некоторые ошибки, связанные со статическими локальными переменными. Я оставил комментарии, где встречаются ошибки!
Для дополнительного контекста я следую руководству по этому проекту, которое вы можете найти здесь.
Вот следующая логика, которую я использую, а также некоторые комментарии для получения дополнительной информации:
// lexer.h
#ifndef LEXER
#define LEXER
struct Lexer;
struct TokenType;
struct Token;
#endif
// parser.h
#ifndef PARSER
#define PARSER
struct Parser;
#endif
// test.cpp, file where we call the Lexer + Parser
Lexer lex { source }; // incomplete type "Lexer" is not allowedC/C++(70)
lex.init_source(); // incomplete type "Lexer" is not allowedC/C++(70)
Parser parse { std::move(lex) }; // incomplete type "Parser" is not allowedC/C++(70)
parse.init(); // incomplete type "Parser" is not allowedC/C++(70)
parse.program(); // incomplete type "Parser" is not allowedC/C++(70)
// lexer.cpp, lexer component of compiler
struct TokenType // need to access this in parser.cpp since we use the enum values there
{
enum Token
{
ENDOFFILE = -1,
NEWLINE = 0,
NUMBER = 1,
... // bunch of other tokens
};
struct Token
{
std::string tokenText {}; // raw text of token
int tokenKind; // enum value of token from TokenType::Token
};
struct Lexer
{
std::string source {}; // source string
size_t curPos = 0; // current index in string array/source, size_t used to ensure no conversions later
char curChar = ' '; // current character in string
... // bunch of other member functions
};
// parser.cpp, parser component of compiler
struct Parser
{
Lexer lex; // attempt to pass Lexer object to Parser, as seen in test.cpp
// the variable declarations here are bugging out and are marked as undefined everywhere where they're used, even though they should have static duration and thus act somewhat like global variables?
void nextToken()
{
static auto peekToken = lex.getToken(); // accessing Lexer struct member functions here, would be able to work if we could actually send the Lexer's components through the member variable above
static auto curToken = peekToken;
}
bool checkToken(auto tokenKind) // enum value of token(s) in TokenType::Token in lexer.cpp
{
return tokenKind == curToken.tokenKind; // error here cuz curToken is undefined
}
bool checkPeek(auto tokenKind)
{
return tokenKind == peekToken.tokenKind; // same error here with peekToken
}
void match(auto tokenKind)
{
if (!(checkToken(tokenKind)))
{
abort("Expected token enum ", tokenKind, ", got ", curToken.tokenKind); // another error here with curToken
}
nextToken();
}
void nl()
{
std::cout << "NEWLINE\n";
match(TokenType::Token::NEWLINE); // incomplete type "TokenType" is not allowedC/C++(70), also: 'TokenType::Token' has not been declared
while (checkToken(TokenType::Token::NEWLINE)) // same errors here as above
{
nextToken();
}
}
void expression()
{
;
}
void statement()
{
if (checkToken(TokenType::Token::PRINT)) // same errors here as above
{
std::cout << "STATEMENT-PRINT\n";
nextToken();
if (checkToken(TokenType::Token::STRING)) // same errors here as above
{
nextToken();
}
else // expression given otherwise
{
expression();
}
}
nl(); // output newline
}
void program()
{
std::cout << "[TRACE] PROGRAM\n";
// parse all statements in program until EOF is reached in source file
while (!(checkToken(TokenType::Token::ENDOFFILE))) // same errors here as above
{
statement();
}
}
void init() // init initializes peekToken and curToken on Parser object initialization
{
nextToken();
nextToken();
}
};
Дайте знать, если вам нужна дополнительная информация или объяснение :)
Вы неправильно используете форвардные объявления, и они все равно не решат вашу проблему.
Предварительное объявление просто утверждает, что тип существует, но не то, как он выглядит. Объявить экземпляр неполного типа нельзя, так как компилятор не будет знать, сколько памяти для него выделить, какие у него конструкторы и т. д.
Упреждающие объявления полезны, когда у вас есть циклические ссылки между классами, но в данном случае это не тот случай, поскольку Lexer
не хочет знать о Parser
.
lexer.h
необходимо объявить полные типы структур Lexer
, Token
и TokenKind
.
lexer.cpp
необходимо определить тела методов Lexer
и т. д.
parser.h
необходимо объявить полный Parser
тип. Сюда входит использование #include "lexer.h"
, чтобы иметь возможность объявить переменную-член Lexer
, использовать TokenType::Token
в параметрах метода и т. д.
parser.cpp
необходимо определить тела методов Parser
и т. д.
Затем вы можете скомпилировать и связать файлы .cpp
, чтобы получить окончательный исполняемый файл.
Вместо этого попробуйте что-нибудь подобное:
лексер.h
#ifndef LEXER_H
#define LEXER_H
#include <string>
struct Token
{
std::string tokenText;
int tokenKind;
};
struct TokenType
{
enum Token
{
ENDOFFILE = -1,
NEWLINE = 0,
NUMBER = 1,
// ...
};
};
struct Lexer
{
std::string source;
size_t curPos = 0;
char curChar = ' ';
// bunch of member function DECLARATIONS ...
};
#endif
лексер.cpp
#include "lexer.h"
// DEFINE Lexer's method bodies here...
parser.h
#ifndef PARSER_H
#define PARSER_H
#include "lexer.h"
struct Parser
{
Lexer lex;
void nextToken();
bool checkToken(TokenType::Token tokenKind);
bool checkPeek(TokenType::Token tokenKind);
void match(TokenType::Token tokenKind);
void nl();
void expression();
void statement();
void program();
void init();
};
#endif
парсер.cpp
#include "parser.h"
void Parser::nextToken()
{
...
}
bool Parser::checkToken(TokenType::Token tokenKind)
{
...
}
bool Parser::checkPeek(TokenType::Token tokenKind)
{
...
}
void Parser::match(TokenType::Token tokenKind)
{
...
}
void Parser::nl()
{
...
}
void Parser::expression()
{
...
}
void Parser::statement()
{
...
}
void Parser::program()
{
...
}
void Parser::init()
{
...
}
test.cpp
#include <string>
#include "lexer.h"
#include "parser.h"
int main()
{
std::string source = ...;
Lexer lex { source };
lex.init_source();
Parser parse { std::move(lex) };
parse.init()
parse.program();
}
Отдельно отметим: использование вами локальных переменных static
внутри Parser::nextToken()
также неверно. Инициализируя переменные в момент их объявления, им будут присвоены значения только один раз — при самом первом вызове nextToken()
на любом Parser
объекте. Последующие вызовы nextToken()
на любом объекте не обновят их.
Для того, что пытается сделать nextToken()
, вам нужно вместо этого использовать нестатические члены класса. Тем более, что Parser::checkToken()
нужен доступ к curToken
, а Parser::checkPeek()
нужен доступ к peekToken
.
Вместо этого попробуйте это:
struct Parser
{
Lexer lex;
Token peekToken;
Token curToken;
...
};
void Parser::nextToken()
{
peekToken = lex.getToken();
curToken = peekToken;
}
bool Parser::checkToken(TokenKind::Token tokenKind)
{
return tokenKind == curToken.tokenKind;
}
bool Parser::checkPeek(TokenKind::Token tokenKind)
{
return tokenKind == peekToken.tokenKind;
}
...
void Parser::init()
{
nextToken();
}
Я предполагаю, что «полный тип лексера» (а также все структуры в данном случае) будет чем-то вроде: ``` struct Lexer { // нужны переменные } ``` Моя единственная проблема здесь в том, что size_t и std ::string живут в отдельных заголовках, так следует ли их #включать и в файлы .h?
@nubbster Да, и да. Я обновил свой ответ примером.
@nubbster — если вы используете имя из стандартной библиотеки, вам нужен #include
заголовок, который его определяет. Иногда компилятор не жалуется на отсутствие включения, но это просто невезение. Стандартным заголовкам разрешено определять дополнительные имена помимо тех, которые они обязаны делать, поэтому код может компилироваться правильно, но это непереносимо.