Read object event gfx info by parsing its C file, rather than relying on regex

This commit is contained in:
Marcus Huderle 2022-07-02 12:40:04 -05:00
parent ed30115d2e
commit 0b0c588c64
16 changed files with 1599 additions and 5 deletions

21
include/lib/fex/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Ashley Coleman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2
include/lib/fex/README Normal file
View file

@ -0,0 +1,2 @@
This is a slightly-modified library for parsing C code originally written by Ashley Coleman
(https://github.com/V-FEXrt).

56
include/lib/fex/array.h Normal file
View file

@ -0,0 +1,56 @@
#ifndef INCLUDE_CORE_ARRAY_H
#define INCLUDE_CORE_ARRAY_H
#include <string>
#include <vector>
#include "array_value.h"
namespace fex
{
class Array
{
public:
Array(std::string type, std::string name) : type_(type), name_(name) {}
void Add(ArrayValue value)
{
values_.push_back(std::move(value));
}
const std::string &type() const
{
return type_;
}
const std::string &name() const
{
return name_;
}
const std::vector<ArrayValue> &values() const
{
return values_;
}
std::vector<ArrayValue> release_values()
{
return std::move(values_);
}
std::string ToString() const
{
std::string out = name_ + ":\n";
for (const ArrayValue &v : values_)
{
out += v.ToString() + "\n";
}
return out;
}
private:
std::string type_;
std::string name_;
std::vector<ArrayValue> values_;
};
} // namespace fex
#endif // INCLUDE_CORE_ARRAY_H

View file

@ -0,0 +1,151 @@
#ifndef INCLUDE_CORE_ARRAY_VALUE_H
#define INCLUDE_CORE_ARRAY_VALUE_H
#include <string>
#include <vector>
#include <memory>
namespace fex
{
class ArrayValue
{
public:
enum class Type
{
kNumber, // Number literal
kString, // String literal
kIdentifier, // Identifier
kValueList, // Value, Value, Value, Value
kValuePair, // Identifier = ArrayValue
kEmpty,
};
Type type() const
{
return type_;
}
const std::vector<ArrayValue> &values() const
{
return values_;
}
std::vector<ArrayValue> release_values()
{
return std::move(values_);
}
const std::pair<std::string, std::unique_ptr<ArrayValue>> &pair() const
{
return pair_;
}
const std::string &string_value() const
{
return string_value_;
}
int int_value() const
{
return int_value_;
}
void set_type(const Type& type)
{
type_ = type;
}
void set_string_value(const std::string& value)
{
string_value_ = value;
}
void set_int_value(int value)
{
int_value_ = value;
}
void set_values(std::vector<ArrayValue> values)
{
values_ = std::move(values);
}
std::string ToString() const
{
switch (type_)
{
case Type::kEmpty:
return "kEmpty: {}";
case Type::kNumber:
return "kNumber: " + std::to_string(int_value_);
case Type::kString:
return "kString: \"" + string_value_ + "\"";
case Type::kIdentifier:
return "kIdentifier: " + string_value_;
case Type::kValueList:
{
std::string out = "kValueList: {\n";
for (const ArrayValue &v : values_)
{
out += "\t" + v.ToString() + ",\n";
}
return out + "}\n";
}
case Type::kValuePair:
return "kValuePair: " + pair_.first + " = " + pair_.second->ToString() + "\n";
}
}
static ArrayValue Empty()
{
return ArrayValue(ArrayValue::Type::kEmpty);
}
static ArrayValue Number(int value)
{
return ArrayValue(ArrayValue::Type::kNumber, value);
}
static ArrayValue String(std::string value)
{
return ArrayValue(ArrayValue::Type::kString, value);
}
static ArrayValue Identifier(std::string value)
{
return ArrayValue(ArrayValue::Type::kIdentifier, value);
}
static ArrayValue ValueList(std::vector<ArrayValue> values)
{
return ArrayValue(ArrayValue::Type::kValueList, std::move(values));
}
static ArrayValue ValuePair(std::pair<std::string, std::unique_ptr<ArrayValue>> value)
{
return ArrayValue(ArrayValue::Type::kValuePair, std::move(value));
}
ArrayValue(Type type) : type_(type) {}
ArrayValue(Type type, int value) : type_(type), int_value_(value) {}
ArrayValue(Type type, std::string value) : type_(type), string_value_(value) {}
ArrayValue(Type type, std::vector<ArrayValue> values) : type_(type), values_(std::move(values)) {}
ArrayValue(Type type, std::pair<std::string, std::unique_ptr<ArrayValue>> pair) : type_(type), pair_(std::move(pair)) {}
ArrayValue() {}
private:
Type type_;
// Number
int int_value_;
// String, Identifier
std::string string_value_;
// ValueList
std::vector<ArrayValue> values_;
// ValuePair
std::pair<std::string, std::unique_ptr<ArrayValue>> pair_ = std::pair<std::string, std::unique_ptr<ArrayValue>>("", nullptr);
};
} // namespace fex
#endif // INCLUDE_CORE_ARRAY_VALUE_H

View file

@ -0,0 +1,22 @@
#ifndef INCLUDE_CORE_DEFINE_STATEMENT_H
#define INCLUDE_CORE_DEFINE_STATEMENT_H
#include <string>
namespace fex
{
class DefineStatement
{
public:
DefineStatement(std::string name, int value) : name_(name), value_(value) {}
const std::string &name() const { return name_; }
int value() const { return value_; }
private:
std::string name_;
int value_;
};
} // namespace fex
#endif // INCLUDE_CORE_DEFINE_STATEMENT_H

121
include/lib/fex/lexer.h Normal file
View file

@ -0,0 +1,121 @@
#ifndef INCLUDE_CORE_LEXER_H
#define INCLUDE_CORE_LEXER_H
#include <string>
#include <vector>
namespace fex
{
class Token
{
public:
enum class Type
{
// Macros
kIfDef,
kIfNDef,
kDefine,
kEndIf,
kInclude,
// Identifiers
kIdentifier,
// Keywords
kExtern,
kConst,
kStruct,
// Literals
kNumber,
kString,
// Symbols
kOpenParen,
kCloseParen,
kLessThan,
kGreaterThan,
kLessThanEqual,
kGreaterThanEqual,
kEqual,
kLeftShift,
kRightShift,
kPlus,
kMinus,
kTimes,
kDivide,
kBitXor,
kBitAnd,
kBitOr,
kLogicalAnd,
kLogicalOr,
kQuote,
kComma,
kSemicolon,
kOpenSquare,
kCloseSquare,
kOpenCurly,
kCloseCurly,
kPeriod,
kUnderscore,
};
Token(Type type, std::string filename, int line_number) : type_(type), filename_(filename), line_number_(line_number) {}
Token(Type type, std::string filename, int line_number, std::string string_value) : type_(type), string_value_(string_value) , filename_(filename), line_number_(line_number) {}
Token(Type type, std::string filename, int line_number, int int_value) : type_(type), int_value_(int_value), filename_(filename), line_number_(line_number) {}
Type type() const { return type_; }
const std::string &string_value() const { return string_value_; }
int int_value() const { return int_value_; }
const std::string &filename() const { return filename_; }
int line_number() const { return line_number_; }
std::string ToString() const;
private:
Type type_;
std::string string_value_;
int int_value_;
std::string filename_ = "";
int line_number_ = 0;
};
class Lexer
{
public:
Lexer() = default;
~Lexer() = default;
std::vector<Token> LexFile(const std::string &path);
std::vector<Token> LexString(const std::string &data);
void LexFileDumpTokens(const std::string &path, const std::string &out);
private:
std::vector<Token> Lex();
char Peek();
char Next();
bool IsNumber();
bool IsAlpha();
bool IsHexAlpha();
bool IsAlphaNumber();
bool IsWhitespace();
Token ConsumeIdentifier();
Token ConsumeKeyword(Token identifier);
Token ConsumeNumber();
Token ConsumeString();
Token ConsumeMacro();
std::string ReadIdentifier();
std::string data_ = "";
uint32_t index_ = 0;
std::string filename_ = "";
int line_number_ = 1;
};
} // namespace fex
#endif // INCLUDE_CORE_LEXER_H

48
include/lib/fex/parser.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef INCLUDE_CORE_PARSER_H
#define INCLUDE_CORE_PARSER_H
#include <map>
#include <string>
#include <vector>
#include "array.h"
#include "array_value.h"
#include "define_statement.h"
#include "lexer.h"
namespace fex
{
class Parser
{
public:
Parser() = default;
std::vector<DefineStatement> Parse(std::vector<Token> tokens);
std::vector<Array> ParseTopLevelArrays(std::vector<Token> tokens);
std::map<std::string, ArrayValue> ParseTopLevelObjects(std::vector<Token> tokens);
std::map<std::string, int> ReadDefines(const std::string &filename, std::vector<std::string> matching);
private:
int EvaluateExpression(std::vector<Token> tokens);
int ResolveIdentifier();
int ResolveIdentifier(const Token &token);
int GetPrecedence(const Token &token);
bool IsOperator(const Token &token);
bool IsParamMacro();
std::vector<Token> ToPostfix();
DefineStatement ParseDefine();
ArrayValue ParseObject();
Token Peek();
Token Next();
unsigned long index_;
std::vector<Token> tokens_;
std::map<std::string, int> top_level_;
};
} // namespace fex
#endif // INCLUDE_CORE_PARSER_H

View file

@ -0,0 +1,19 @@
#ifndef PARSER_UTIL_H
#define PARSER_UTIL_H
#include <QString>
#include <QStringList>
class ParserUtil
{
public:
ParserUtil(QString root);
QStringList ReadDefines(QString filename, QString prefix);
QStringList ReadDefinesValueSort(QString filename, QString prefix);
private:
QString root_;
};
#endif // PARSER_UTIL_H

View file

@ -187,6 +187,7 @@ public:
bool readObjEventGfxConstants();
bool readSongNames();
bool readEventGraphics();
QMap<QString, QMap<QString, QString>> readObjEventGfxInfo();
void setEventPixmap(Event * event, bool forceLoad = false);

View file

@ -32,6 +32,9 @@ SOURCES += src/core/block.cpp \
src/core/regionmap.cpp \
src/core/wildmoninfo.cpp \
src/core/editcommands.cpp \
src/lib/fex/lexer.cpp \
src/lib/fex/parser.cpp \
src/lib/fex/parser_util.cpp \
src/lib/orderedjson.cpp \
src/core/regionmapeditcommands.cpp \
src/mainwindow_scriptapi.cpp \
@ -108,6 +111,12 @@ HEADERS += include/core/block.h \
include/core/wildmoninfo.h \
include/core/editcommands.h \
include/core/regionmapeditcommands.h \
include/lib/fex/array.h \
include/lib/fex/array_value.h \
include/lib/fex/define_statement.h \
include/lib/fex/lexer.h \
include/lib/fex/parser.h \
include/lib/fex/parser_util.h \
include/lib/orderedmap.h \
include/lib/orderedjson.h \
include/ui/aboutporymap.h \

21
src/lib/fex/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Ashley Coleman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2
src/lib/fex/README Normal file
View file

@ -0,0 +1,2 @@
This is a slightly-modified library for parsing C code originally written by Ashley Coleman
(https://github.com/V-FEXrt).

505
src/lib/fex/lexer.cpp Normal file
View file

@ -0,0 +1,505 @@
#include "lib/fex/lexer.h"
#include <fstream>
#include <iostream>
#include <sstream>
namespace fex
{
bool Lexer::IsNumber()
{
char c = Peek();
return (c >= '0' && c <= '9');
}
bool Lexer::IsWhitespace()
{
char c = Peek();
return (c == ' ' || c == '\t' || c == '\r' || c == '\n');
}
bool Lexer::IsHexAlpha()
{
char c = Peek();
return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
}
bool Lexer::IsAlpha()
{
char c = Peek();
return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
}
bool Lexer::IsAlphaNumber()
{
return IsAlpha() || IsNumber();
};
char Lexer::Peek()
{
return data_[index_];
}
char Lexer::Next()
{
char c = Peek();
index_++;
return c;
}
Token Lexer::ConsumeKeyword(Token identifier)
{
const std::string &value = identifier.string_value();
if (value == "extern")
{
return Token(Token::Type::kExtern, identifier.filename(), identifier.line_number());
}
if (value == "const")
{
return Token(Token::Type::kConst, identifier.filename(), identifier.line_number());
}
if (value == "struct")
{
return Token(Token::Type::kStruct, identifier.filename(), identifier.line_number());
}
return identifier;
}
Token Lexer::ConsumeIdentifier()
{
std::string identifer = "";
while (IsAlphaNumber() || Peek() == '_')
{
identifer += Next();
}
return ConsumeKeyword(Token(Token::Type::kIdentifier, filename_, line_number_, identifer));
}
Token Lexer::ConsumeNumber()
{
std::string identifer = "";
if (Peek() == '0')
{
identifer += Next();
if (Peek() == 'x')
{
identifer += Next();
}
while (IsNumber() || IsHexAlpha())
{
identifer += Next();
}
return Token(Token::Type::kNumber, filename_, line_number_, std::stoi(identifer, nullptr, 16));
}
while (IsNumber())
{
identifer += Next();
}
return Token(Token::Type::kNumber, filename_, line_number_, std::stoi(identifer));
}
// TODO: Doesn't currently support escape characters
Token Lexer::ConsumeString()
{
std::string value = "";
if (Next() != '\"')
{
// Error
}
// TODO: error if we never see a quote
while (Peek() != '\"')
{
value += Next();
}
Next(); // Consume final quote
return Token(Token::Type::kString, filename_, line_number_, value);
}
Token Lexer::ConsumeMacro()
{
Token id = ConsumeIdentifier();
if (id.string_value() == "ifdef")
{
return Token(Token::Type::kIfDef, filename_, line_number_);
}
if (id.string_value() == "ifndef")
{
return Token(Token::Type::kIfNDef, filename_, line_number_);
}
if (id.string_value() == "define")
{
return Token(Token::Type::kDefine, filename_, line_number_);
}
if (id.string_value() == "endif")
{
return Token(Token::Type::kEndIf, filename_, line_number_);
}
if (id.string_value() == "include")
{
return Token(Token::Type::kInclude, filename_, line_number_);
}
return Token(Token::Type::kDefine, filename_, line_number_);
}
std::vector<Token> Lexer::LexString(const std::string &data)
{
filename_ = "string literal";
line_number_ = 1;
index_ = 0;
data_ = data;
return Lex();
}
std::vector<Token> Lexer::LexFile(const std::string &path)
{
filename_ = path;
line_number_ = 1;
std::ifstream file;
file.open(path);
std::stringstream stream;
stream << file.rdbuf();
index_ = 0;
data_ = stream.str();
file.close();
return Lex();
}
void Lexer::LexFileDumpTokens(const std::string &path, const std::string &out)
{
std::ofstream file;
file.open(out);
for (Token token : LexFile(path))
{
file << token.ToString() << std::endl;
}
file.close();
}
std::vector<Token> Lexer::Lex()
{
std::vector<Token> tokens;
while (index_ < data_.length())
{
while (IsWhitespace())
{
if (Peek() == '\n')
{
line_number_++;
}
Next();
}
if (IsAlpha())
{
tokens.push_back(ConsumeIdentifier());
continue;
}
if (IsNumber())
{
tokens.push_back(ConsumeNumber());
continue;
}
switch (Peek())
{
case '*':
Next();
tokens.push_back(Token(Token::Type::kTimes, filename_, line_number_));
break;
case '-':
Next();
tokens.push_back(Token(Token::Type::kMinus, filename_, line_number_));
break;
case '+':
Next();
tokens.push_back(Token(Token::Type::kPlus, filename_, line_number_));
break;
case '(':
Next();
tokens.push_back(Token(Token::Type::kOpenParen, filename_, line_number_));
break;
case ')':
Next();
tokens.push_back(Token(Token::Type::kCloseParen, filename_, line_number_));
break;
case '&':
Next();
if (Peek() == '&')
{
Next();
tokens.push_back(Token(Token::Type::kLogicalAnd, filename_, line_number_));
break;
}
tokens.push_back(Token(Token::Type::kBitAnd, filename_, line_number_));
break;
case '|':
Next();
if (Peek() == '|')
{
Next();
tokens.push_back(Token(Token::Type::kLogicalOr, filename_, line_number_));
break;
}
tokens.push_back(Token(Token::Type::kBitOr, filename_, line_number_));
break;
case '^':
Next();
tokens.push_back(Token(Token::Type::kBitXor, filename_, line_number_));
break;
case ',':
Next();
tokens.push_back(Token(Token::Type::kComma, filename_, line_number_));
break;
case '=':
Next();
tokens.push_back(Token(Token::Type::kEqual, filename_, line_number_));
break;
case ';':
Next();
tokens.push_back(Token(Token::Type::kSemicolon, filename_, line_number_));
break;
case '[':
Next();
tokens.push_back(Token(Token::Type::kOpenSquare, filename_, line_number_));
break;
case ']':
Next();
tokens.push_back(Token(Token::Type::kCloseSquare, filename_, line_number_));
break;
case '{':
Next();
tokens.push_back(Token(Token::Type::kOpenCurly, filename_, line_number_));
break;
case '}':
Next();
tokens.push_back(Token(Token::Type::kCloseCurly, filename_, line_number_));
break;
case '.':
Next();
tokens.push_back(Token(Token::Type::kPeriod, filename_, line_number_));
break;
case '_':
Next();
tokens.push_back(Token(Token::Type::kUnderscore, filename_, line_number_));
break;
case '#':
Next();
tokens.push_back(ConsumeMacro());
break;
case '\"':
tokens.push_back(ConsumeString());
break;
case '<':
Next();
if (Peek() == '<')
{
Next();
tokens.push_back(Token(Token::Type::kLeftShift, filename_, line_number_));
break;
}
if (Peek() == '=')
{
Next();
tokens.push_back(Token(Token::Type::kLessThanEqual, filename_, line_number_));
break;
}
tokens.push_back(Token(Token::Type::kLessThan, filename_, line_number_));
break;
case '>':
Next();
if (Peek() == '>')
{
Next();
tokens.push_back(Token(Token::Type::kRightShift, filename_, line_number_));
break;
}
if (Peek() == '=')
{
Next();
tokens.push_back(Token(Token::Type::kGreaterThanEqual, filename_, line_number_));
break;
}
tokens.push_back(Token(Token::Type::kGreaterThan, filename_, line_number_));
break;
case '/':
Next();
switch (Peek())
{
case '/':
while (Next() != '\n')
;
continue;
case '*':
while (Next() != '*')
;
Next(); // last /
continue;
default:
tokens.push_back(Token(Token::Type::kDivide, filename_, line_number_));
continue;
}
case '\0':
Next();
break;
default:
char c = Next();
std::cout << "[WARNING] Unable to lex unknown char: '" << c << "' (0x" << std::hex << (int)c << ")" << std::endl;
break;
}
}
return tokens;
}
std::string Token::ToString() const
{
std::string out = filename() + ":" + std::to_string(line_number()) + " - ";
switch (type())
{
case Token::Type::kIfDef:
out += "Macro: IfDef";
break;
case Token::Type::kIfNDef:
out += "Macro: IfNDef";
break;
case Token::Type::kDefine:
out += "Macro: Define";
break;
case Token::Type::kEndIf:
out += "Macro: EndIf";
break;
case Token::Type::kInclude:
out += "Macro: Include";
break;
case Token::Type::kNumber:
out += "Number: " + std::to_string(int_value());
break;
case Token::Type::kString:
out += "String: " + string_value();
break;
case Token::Type::kIdentifier:
out += "Identifier: " + string_value();
break;
case Token::Type::kOpenParen:
out += "Symbol: (";
break;
case Token::Type::kCloseParen:
out += "Symbol: )";
break;
case Token::Type::kLessThan:
out += "Symbol: <";
break;
case Token::Type::kGreaterThan:
out += "Symbol: >";
break;
case Token::Type::kLeftShift:
out += "Symbol: <<";
break;
case Token::Type::kRightShift:
out += "Symbol: >>";
break;
case Token::Type::kPlus:
out += "Symbol: +";
break;
case Token::Type::kMinus:
out += "Symbol: -";
break;
case Token::Type::kTimes:
out += "Symbol: *";
break;
case Token::Type::kDivide:
out += "Symbol: /";
break;
case Token::Type::kBitXor:
out += "Symbol: ^";
break;
case Token::Type::kBitAnd:
out += "Symbol: &";
break;
case Token::Type::kBitOr:
out += "Symbol: |";
break;
case Token::Type::kQuote:
out += "Symbol: \"";
break;
case Token::Type::kComma:
out += "Symbol: ,";
break;
case Token::Type::kLessThanEqual:
out += "Symbol: <=";
break;
case Token::Type::kGreaterThanEqual:
out += "Symbol: >=";
break;
case Token::Type::kEqual:
out += "Symbol: =";
break;
case Token::Type::kLogicalAnd:
out += "Symbol: &&";
break;
case Token::Type::kLogicalOr:
out += "Symbol: ||";
break;
case Token::Type::kSemicolon:
out += "Symbol: ;";
break;
case Token::Type::kExtern:
out += "Keyword: extern";
break;
case Token::Type::kConst:
out += "Keyword: const";
break;
case Token::Type::kStruct:
out += "Keyword: struct";
break;
case Token::Type::kOpenSquare:
out += "Symbol: [";
break;
case Token::Type::kCloseSquare:
out += "Symbol: ]";
break;
case Token::Type::kOpenCurly:
out += "Symbol: {";
break;
case Token::Type::kCloseCurly:
out += "Symbol: }";
break;
case Token::Type::kPeriod:
out += "Symbol: .";
break;
case Token::Type::kUnderscore:
out += "Symbol: _";
break;
}
return out;
}
} // namespace fex

538
src/lib/fex/parser.cpp Normal file
View file

@ -0,0 +1,538 @@
#include "lib/fex/parser.h"
#include <iostream>
#include <regex>
#include <vector>
namespace fex
{
int Parser::GetPrecedence(const Token &token)
{
switch (token.type())
{
case Token::Type::kTimes:
return 3;
case Token::Type::kDivide:
return 3;
case Token::Type::kPlus:
return 4;
case Token::Type::kMinus:
return 4;
case Token::Type::kLeftShift:
return 5;
case Token::Type::kRightShift:
return 5;
case Token::Type::kBitAnd:
return 8;
case Token::Type::kBitXor:
return 9;
case Token::Type::kBitOr:
return 10;
default:
{
std::cout << "Asked for precedence of unmapped token: " << token.ToString() << std::endl;
return 0;
}
}
}
std::vector<Token> Parser::ToPostfix()
{
std::vector<Token::Type> types = {
Token::Type::kNumber,
Token::Type::kIdentifier,
Token::Type::kOpenParen,
Token::Type::kCloseParen,
Token::Type::kLeftShift,
Token::Type::kRightShift,
Token::Type::kPlus,
Token::Type::kMinus,
Token::Type::kTimes,
Token::Type::kDivide,
Token::Type::kBitXor,
Token::Type::kBitAnd,
Token::Type::kBitOr,
};
std::vector<Token> output;
std::vector<Token> stack;
while (std::find(types.begin(), types.end(), Peek().type()) != types.end())
{
Token token = Next();
if (token.type() == Token::Type::kNumber || token.type() == Token::Type::kIdentifier)
{
output.push_back(token);
}
else if (token.type() == Token::Type::kOpenParen)
{
stack.push_back(token);
}
else if (token.type() == Token::Type::kCloseParen)
{
while (!stack.empty() && stack.back().type() != Token::Type::kOpenParen)
{
Token back = stack.back();
stack.pop_back();
output.push_back(back);
}
// Next();
if (!stack.empty())
{
// pop the left parenthesis token
stack.pop_back();
}
else
{
std::cout << "Mismatched parentheses detected in expression!" << std::endl;
}
}
else
{
// token is an operator
while (!stack.empty() && stack.back().type() != Token::Type::kOpenParen && GetPrecedence(stack.back()) <= GetPrecedence(token))
{
Token back = stack.back();
stack.pop_back();
output.push_back(back);
}
stack.push_back(token);
}
}
while (!stack.empty())
{
if (stack.back().type() == Token::Type::kOpenParen || stack.back().type() == Token::Type::kCloseParen)
{
std::cout << "Mismatched parentheses detected in expression!" << std::endl;
}
else
{
Token back = stack.back();
stack.pop_back();
output.push_back(back);
}
}
return output;
}
Token Parser::Peek() { return tokens_[index_]; }
Token Parser::Next()
{
Token t = Peek();
index_++;
return t;
}
int Parser::ResolveIdentifier(const Token &token)
{
std::string iden_val = token.string_value();
if (top_level_.find(iden_val) == top_level_.end())
{
std::cout << "[WARNING] Unknown identifier " << iden_val << std::endl;
return 0;
}
return top_level_[iden_val];
}
bool Parser::IsOperator(const Token &token)
{
std::vector<Token::Type> types = {
Token::Type::kLeftShift,
Token::Type::kRightShift,
Token::Type::kPlus,
Token::Type::kMinus,
Token::Type::kTimes,
Token::Type::kDivide,
Token::Type::kBitXor,
Token::Type::kBitAnd,
Token::Type::kBitOr,
};
return std::find(types.begin(), types.end(), token.type()) != types.end();
}
int Parser::EvaluateExpression(std::vector<Token> tokens)
{
std::vector<Token> stack;
for (Token token : tokens)
{
if (IsOperator(token) && stack.size() > 1)
{
int op2 = stack.back().int_value();
stack.pop_back();
int op1 = stack.back().int_value();
stack.pop_back();
int result = 0;
if (token.type() == Token::Type::kTimes)
{
result = op1 * op2;
}
if (token.type() == Token::Type::kDivide)
{
result = op1 / op2;
}
if (token.type() == Token::Type::kPlus)
{
result = op1 + op2;
}
if (token.type() == Token::Type::kMinus)
{
result = op1 - op2;
}
if (token.type() == Token::Type::kLeftShift)
{
result = op1 << op2;
}
if (token.type() == Token::Type::kRightShift)
{
result = op1 >> op2;
}
if (token.type() == Token::Type::kBitAnd)
{
result = op1 & op2;
}
if (token.type() == Token::Type::kBitXor)
{
result = op1 ^ op2;
}
if (token.type() == Token::Type::kBitOr)
{
result = op1 | op2;
}
stack.push_back(Token(Token::Type::kNumber, token.filename(), token.line_number(), result));
}
if (token.type() == Token::Type::kNumber)
{
stack.push_back(token);
}
if (token.type() == Token::Type::kIdentifier)
{
stack.push_back(Token(Token::Type::kNumber, token.filename(), token.line_number(), ResolveIdentifier(token)));
}
}
return stack.size() ? stack.back().int_value() : 0;
}
bool Parser::IsParamMacro()
{
int save_index = index_;
if (Peek().type() != Token::Type::kOpenParen)
{
return false;
}
Next(); // Consume open so next if doesn't see it
while (Peek().type() != Token::Type::kCloseParen)
{
// Nested parens aren't allowed in param list
if (Peek().type() == Token::Type::kOpenParen)
{
index_ = save_index;
return false;
}
Next();
}
// Consume closing
Next();
std::vector<Token::Type> types = {
Token::Type::kNumber,
Token::Type::kIdentifier,
Token::Type::kOpenParen,
Token::Type::kCloseParen,
Token::Type::kLeftShift,
Token::Type::kRightShift,
Token::Type::kPlus,
Token::Type::kMinus,
Token::Type::kTimes,
Token::Type::kDivide,
Token::Type::kBitXor,
Token::Type::kBitAnd,
Token::Type::kBitOr,
};
// read value before resetting.
bool out = std::find(types.begin(), types.end(), Peek().type()) != types.end();
index_ = save_index;
return out;
}
DefineStatement Parser::ParseDefine()
{
if (Next().type() != Token::Type::kDefine)
{
// error
}
if (Peek().type() != Token::Type::kIdentifier)
{
// error
}
std::string identifer = Next().string_value();
int value = 0;
if (IsParamMacro())
{
std::cout << "[WARNING] Macro:" << identifer << " has parameters which is not currently supported. Returning a dummy value instead." << std::endl;
value = 0xDEAD;
// Parameters (x, y, x) Expression
Next();
while (Peek().type() != Token::Type::kCloseParen)
{
auto formal = Next().string_value();
if (Peek().type() == Token::Type::kComma)
{
Next();
}
}
Next();
// In all current use cases, the macro is #define MACRO(a, b, c) (( something ))
// we have consumed through the parameter list at this point. Consume all remaing
// contents inside of parens
if (Peek().type() != Token::Type::kOpenParen)
{
std::cout << "[FATAL] Must seen open parenthesis to continue processing." << std::endl;
abort();
}
Next();
int paren_count = 1;
while (paren_count > 0)
{
if (Peek().type() == Token::Type::kOpenParen)
{
paren_count++;
}
if (Peek().type() == Token::Type::kCloseParen)
{
paren_count--;
}
Next();
}
}
else
{
value = EvaluateExpression(ToPostfix());
}
top_level_[identifer] = value;
return DefineStatement(identifer, value);
}
std::map<std::string, int> Parser::ReadDefines(const std::string &filename, std::vector<std::string> matching)
{
std::map<std::string, int> out;
Lexer lexer;
auto tokens = lexer.LexFile(filename);
auto defines = Parse(tokens);
for (const auto &define : defines)
{
for (const std::string &match : matching)
{
if (std::regex_match(define.name(), std::regex(match)))
{
out[define.name()] = define.value();
}
}
}
return out;
}
ArrayValue Parser::ParseObject()
{
if (Peek().type() == Token::Type::kOpenSquare)
{
Next(); // [
std::string identifier = Next().string_value();
Next(); // ]
Next(); // =
std::unique_ptr<ArrayValue> value = std::unique_ptr<ArrayValue>(new ArrayValue(ParseObject()));
std::pair<std::string, std::unique_ptr<ArrayValue>> pair(identifier, std::move(value));
return ArrayValue::ValuePair(std::move(pair));
}
if (Peek().type() == Token::Type::kOpenCurly)
{
std::vector<ArrayValue> values;
Next(); // {
values.push_back(ParseObject());
while(Peek().type() == Token::Type::kComma) {
Next();
values.push_back(ParseObject());
}
Next(); // }
if (values.size() == 1) {
return std::move(values[0]);
}
return ArrayValue::ValueList(std::move(values));
}
if (Peek().type() == Token::Type::kNumber)
{
int value = Next().int_value();
return ArrayValue::Number(value);
}
if (Peek().type() == Token::Type::kBitAnd)
{
// Just skip past any reference indicators before identifiers.
// This is not the right way to handle this, but it's good enough
// for our parsing needs.
Next(); // &
}
if (Peek().type() == Token::Type::kIdentifier)
{
std::vector<ArrayValue> idens = {};
idens.push_back(ArrayValue::Identifier(Next().string_value()));
// NELEMS(...)
if (Peek().type() == Token::Type::kOpenParen)
{
while (Peek().type() != Token::Type::kCloseParen)
{
std::string out = Next().ToString();
}
Next(); // )
}
// ABC | DEF | GHI
while (Peek().type() == Token::Type::kBitOr) {
Next();
idens.push_back(ArrayValue::Identifier(Next().string_value()));
}
if (idens.size() == 1)
{
return std::move(idens[0]);
}
return ArrayValue::ValueList(std::move(idens));
}
if (Peek().type() == Token::Type::kUnderscore)
{
Next(); // _
Next(); // (
std::string value = Next().string_value();
Next(); // )
return ArrayValue::String(value);
}
if (Peek().type() == Token::Type::kPeriod)
{
Next(); // .
std::string identifier = Next().string_value();
Next(); // =
std::unique_ptr<ArrayValue> value = std::unique_ptr<ArrayValue>(new ArrayValue(ParseObject()));
std::pair<std::string, std::unique_ptr<ArrayValue>> pair(identifier, std::move(value));
return ArrayValue::ValuePair(std::move(pair));
}
return ArrayValue::Empty();
}
std::vector<Array> Parser::ParseTopLevelArrays(std::vector<Token> tokens)
{
index_ = 0;
tokens_ = std::move(tokens);
std::vector<Array> items;
while (index_ < tokens_.size())
{
while (Next().type() != Token::Type::kConst)
;
Next(); // struct
std::string type = Next().string_value();
std::string name = Next().string_value();
Array value(type, name);
Next(); // [
Next(); // ]
Next(); // =
value.Add(ParseObject());
Next(); // ;
items.push_back(std::move(value));
}
return items;
}
std::map<std::string, ArrayValue> Parser::ParseTopLevelObjects(std::vector<Token> tokens)
{
index_ = 0;
tokens_ = std::move(tokens);
std::map<std::string, ArrayValue> items;
while (index_ < tokens_.size())
{
while (Next().type() != Token::Type::kConst)
;
Next(); // struct
Next(); // type
std::string name = Next().string_value();
Next(); // =
items[name] = ParseObject();
Next(); // ;
}
return items;
}
std::vector<DefineStatement> Parser::Parse(std::vector<Token> tokens)
{
index_ = 0;
tokens_ = std::move(tokens);
std::vector<DefineStatement> statements;
while (index_ < tokens_.size())
{
switch (Peek().type())
{
case Token::Type::kDefine:
statements.push_back(ParseDefine());
break;
default:
Next();
break;
}
}
return statements;
}
} // namespace fex

View file

@ -0,0 +1,50 @@
#include "lib/fex/parser_util.h"
#include <QMap>
#include "lib/fex/parser.h"
ParserUtil::ParserUtil(QString root): root_(root) {}
QStringList ParserUtil::ReadDefines(QString filename, QString prefix)
{
if (filename.isEmpty()) {
return QStringList();
}
QString filepath = root_ + "/" + filename;
fex::Parser parser;
std::vector<std::string> match_list = { prefix.toStdString() + ".*" };
std::map<std::string, int> defines = parser.ReadDefines(filepath.toStdString(), match_list);
QStringList out;
for(auto const& define : defines) {
out.append(QString::fromStdString(define.first));
}
return out;
}
QStringList ParserUtil::ReadDefinesValueSort(QString filename, QString prefix)
{
if (filename.isEmpty()) {
return QStringList();
}
QString filepath = root_ + "/" + filename;
fex::Parser parser;
std::vector<std::string> match_list = { prefix.toStdString() + ".*" };
std::map<std::string, int> defines = parser.ReadDefines(filepath.toStdString(), match_list);
QMultiMap<int, QString> defines_keyed_by_value;
for (const auto& pair : defines) {
defines_keyed_by_value.insert(pair.second, QString::fromStdString(pair.first));
}
return defines_keyed_by_value.values();
}

View file

@ -10,6 +10,8 @@
#include "map.h"
#include "orderedjson.h"
#include "lib/fex/lexer.h"
#include "lib/fex/parser.h"
#include <QDir>
#include <QJsonArray>
@ -2471,16 +2473,20 @@ bool Project::readEventGraphics() {
qDeleteAll(eventGraphicsMap);
eventGraphicsMap.clear();
QStringList gfxNames = gfxDefines.keys();
QMap<QString, QMap<QString, QString>> gfxInfos = readObjEventGfxInfo();
for (QString gfxName : gfxNames) {
EventGraphics * eventGraphics = new EventGraphics;
QString info_label = pointerHash[gfxName].replace("&", "");
QStringList gfx_info = parser.readCArray("src/data/object_events/object_event_graphics_info.h", info_label);
if (!gfxInfos.contains(info_label))
continue;
eventGraphics->inanimate = (gfx_info.value(8) == "TRUE");
QString pic_label = gfx_info.value(14);
QString dimensions_label = gfx_info.value(11);
QString subsprites_label = gfx_info.value(12);
QMap<QString, QString>gfxInfoAttributes = gfxInfos[info_label];
eventGraphics->inanimate = gfxInfoAttributes.value("inanimate") == "TRUE";
QString pic_label = gfxInfoAttributes.value("images");
QString dimensions_label = gfxInfoAttributes.value("oam");
QString subsprites_label = gfxInfoAttributes.value("subspriteTables");
QString gfx_label = parser.readCArray("src/data/object_events/object_event_pic_tables.h", pic_label).value(0);
gfx_label = gfx_label.section(QRegularExpression("[\\(\\)]"), 1, 1);
@ -2515,6 +2521,28 @@ bool Project::readEventGraphics() {
return true;
}
QMap<QString, QMap<QString, QString>> Project::readObjEventGfxInfo() {
// TODO: refactor this to be more general if we end up directly parsing C
// for more use cases in the future.
auto cParser = fex::Parser();
auto tokens = fex::Lexer().LexFile((root + "/src/data/object_events/object_event_graphics_info.h").toStdString());
auto gfxInfoObjects = cParser.ParseTopLevelObjects(tokens);
QMap<QString, QMap<QString, QString>> gfxInfos;
for (auto it = gfxInfoObjects.begin(); it != gfxInfoObjects.end(); it++) {
QMap<QString, QString> values;
for (const fex::ArrayValue &v : it->second.values()) {
if (v.type() != fex::ArrayValue::Type::kValuePair)
continue;
QString key = QString::fromStdString(v.pair().first);
QString value = QString::fromStdString(v.pair().second->string_value());
values.insert(key, value);
}
gfxInfos.insert(QString::fromStdString(it->first), values);
}
return gfxInfos;
}
bool Project::readSpeciesIconPaths() {
speciesToIconPath.clear();
QString srcfilename = "src/pokemon_icon.c";