start of XML based msgdata

This commit is contained in:
red031000 2023-06-06 22:58:19 +01:00
parent 875c5a5d89
commit 64b73d6b02
No known key found for this signature in database
GPG Key ID: D27E50C050AE0CE1
45 changed files with 27354 additions and 14601 deletions

View File

@ -19,7 +19,7 @@ jobs:
run: |
sudo apt update
sudo ACCEPT_EULA=Y apt -y --fix-missing --allow-downgrades upgrade
sudo apt -y --allow-downgrades install g++-10-multilib linux-libc-dev binutils-arm-none-eabi p7zip-full pkg-config ppa-purge
sudo apt -y --allow-downgrades install g++-10-multilib linux-libc-dev binutils-arm-none-eabi p7zip-full pkg-config ppa-purge libpugixml-dev
sudo ppa-purge -y ppa:ubuntu-toolchain-r/test
sudo dpkg --add-architecture i386
sudo mkdir -pm755 /etc/apt/keyrings

View File

@ -22,6 +22,7 @@ Building the ROM requires the following packages:
* python3 (for asm preprocessor)
* libpng-devel (libpng-dev on Ubuntu)
* pkg-config
* pugixml (libpugixml-dev on Ubuntu)
NOTE: If you are using Arch/Manjaro or Void you will only need base-devel instead of build-essentials or make or git. You will still need wine.
@ -50,6 +51,7 @@ You will still require the following packages:
* build-essentials
* libpng-devel
* pkg-config
* pugixml
Install them using either the Cygwin package manager or using pacman on Msys2.
@ -68,13 +70,14 @@ macOS 10.15 Catalina and later is supported on Intel and ARM64 hardware configur
* libpng
* pkg-config
* wine-crossover (includes wine32on64, required on Catalina and later to run 32-bit x86 EXEs)
* pugixml
They can be installed with the following commands:
```console
$ brew tap osx-cross/homebrew-arm
$ brew tap gcenx/wine
$ brew install coreutils make gnu-sed llvm arm-gcc-bin libpng git pkg-config
$ brew install coreutils make gnu-sed llvm arm-gcc-bin libpng git pkg-config pugixml
$ brew install wine-crossover
```

View File

@ -168,7 +168,7 @@ clean: mostlyclean clean-fs clean-tools
clean-fs:
$(RM) $(filter %.narc %.arc,$(HOSTFS_FILES))
$(RM) $(patsubst %.narc,%.naix,$(patsubst %.arc,%.naix,$(filter %.narc %.arc,$(HOSTFS_FILES))))
$(RM) $(NCGR_CLEAN_LIST) $(NCLR_CLEAN_LIST) $(NCER_CLEAN_LIST)
$(RM) $(NCGR_CLEAN_LIST) $(NCLR_CLEAN_LIST) $(NCER_CLEAN_LIST) $(FS_CLEAN_LIST)
find . \( -iname '*.1bpp' -o -iname '*.4bpp' -o -iname '*.8bpp' -o -iname '*.gbapal' -o -iname '*.lz' \) -exec $(RM) {} +
$(RM) files/msgdata/msg/narc_*.bin

View File

@ -1,8 +1,22 @@
CHARMAP = charmap.txt
MSGDATA_DIR := files/msgdata
MSGDATA_MSG_DIR := $(MSGDATA_DIR)/msg
TRNAME_GMM := $(MSGDATA_MSG_DIR)/narc_0559.gmm
MSGFILE_GMM := $(sort $(wildcard $(MSGDATA_MSG_DIR)/*.gmm) $(TRNAME_GMM))
MSGFILE_BIN := $(patsubst %.gmm,%.bin,$(MSGFILE_GMM))
MSGFILE_H := $(patsubst %.gmm,%.h,$(MSGFILE_GMM))
$(MSGFILE_H): %.h: %.bin
## Trainer names
files/msgdata/msg/narc_0559.txt: files/poketool/trainer/trdata.json
(echo " -"; $(GREP) -w '"name":' $< | cut -d'"' -f4) | $(SED) 's/^(.+)$$/{TRNAME}\1\r/g' > $@
$(TRNAME_GMM): files/poketool/trainer/trdata.json files/poketool/trainer/trname.json.txt
$(JSONPROC) $^ $@
$(SED) -i 's/&/&amp;/g' $@
$(MSGFILE_BIN): %.bin: %.gmm $(CHARMAP)
$(MSGENC) $(MSGENCFLAGS) -e -c $(CHARMAP) --gmm -H $*.h $< $@
files/msgdata/msg/%.bin: files/msgdata/msg/%.txt $(CHARMAP)
$(MSGENC) $(MSGENCFLAGS) -e -c $(CHARMAP) $< $@
@ -631,3 +645,5 @@ files/msgdata/msg/narc_0620.bin: MSGENCFLAGS = -k 0x1395
files/msgdata/msg/narc_0621.bin: MSGENCFLAGS = -k 0xbea9
files/msgdata/msg/narc_0622.bin: MSGENCFLAGS = -k 0x7df9
files/msgdata/msg/narc_0623.bin: MSGENCFLAGS = -k 0x5938
FS_CLEAN_LIST += $(TRNAME_GMM) $(MSGFILE_H)

View File

@ -1,2 +1,3 @@
*.bin
narc_0559.txt
narc_0559.gmm
*.h

View File

@ -2,3 +2,5 @@
*.pal
*.txt
*.key
*.h
*.gmm

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@
u32 __size = sizeof(struct TrainerData);
const struct TrainerData __data[] = {
{},
## for trainer in trdata
{
// Trainer #{{trainer.index}}, {{ trainer.class }} {{ trainer.name }}

View File

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<body language="English">{% set i=0 %}
## for trainer in trdata
<row id="msg_0559_{{ fmtInt( i, 5, 2 ) }}" index="{{ i }}">
<attribute name="window_context_name">used</attribute>
<language name="English">{{ trainer.name }}</language>
</row>{% set i=i+1 %}
## endfor
</body>

View File

@ -8,13 +8,13 @@
#define max(a, b) ((a) >= (b) ? (a) : (b))
u32 __size[] = { 8,
u32 __size[] = {
## for trainer in trdata
(max({{ length(trainer.party) }} * {% if trainer.type == "TRTYPE_MON" %}6{% else if trainer.type == "TRTYPE_MON_MOVES" %}14{% else if trainer.type == "TRTYPE_MONE_ITEM" %}8{% else %}16{% endif %}, 8) + 3) & ~3,
## endfor
};
const u16 __data[] = { 0, 0, 0, 0,
const u16 __data[] = {
## for trainer in trdata
// Trainer #{{ trainer.index }}, {{ trainer.class }} {{ trainer.name }}
## if trainer.type == "TRTYPE_MON"
@ -45,8 +45,12 @@ const u16 __data[] = { 0, 0, 0, 0,
## endfor
## endif
## endfor
## if total_length < 4 or total_length % 2 != 0
## if total_length < 4
## for i in range(4 - total_length)
0, // Padding
## endfor
## else if total_length % 2 != 0
0, //Padding
## endif
## endfor
};

View File

@ -319,7 +319,7 @@ $(O2NARC_TARGETS): %.narc: %.json %.json.txt $$(dep)
$(JSONPROC) $*.json $*.json.txt $*.c
$(CC) $(MWCFLAGS) -c -o $*.o $*.c
$(O2NARC) $(O2NARCFLAGS) $*.o $@
@$(RM) $*.o $*.c
@$(RM) $*.o $*.c $*/*.c $*/*.o
files/application/wifi_earth/wifi_earth.narc: \
files/application/wifi_earth/wifi_earth/narc_0005.NCGR \
@ -2707,7 +2707,7 @@ files/poketool/icongra/poke_icon.narc: \
files/poketool/icongra/poke_icon/narc_0402.NCGR
files/msgdata/msg.narc: \
files/msgdata/msg/narc_0000.bin \
files/msgdata/msg/narc_0000.bin \
files/msgdata/msg/narc_0001.bin \
files/msgdata/msg/narc_0002.bin \
files/msgdata/msg/narc_0003.bin \

View File

@ -4,7 +4,22 @@ INCLUDES := -I .
SRCS := jsonproc.cpp
HEADERS := jsonproc.h inja.hpp nlohmann/json.hpp
HEADERS := jsonproc.h \
inja.hpp \
config.hpp \
environment.hpp \
exceptions.hpp \
function_storage.hpp \
lexer.hpp \
node.hpp \
parser.hpp \
renderer.hpp \
statistics.hpp \
string_view.hpp \
template.hpp \
token.hpp \
utils.hpp \
nlohmann/json.hpp
.PHONY: all clean

79
tools/jsonproc/config.hpp Normal file
View File

@ -0,0 +1,79 @@
#ifndef INCLUDE_INJA_CONFIG_HPP_
#define INCLUDE_INJA_CONFIG_HPP_
#include <functional>
#include <string>
#include "string_view.hpp"
namespace inja {
/*!
* \brief Class for lexer configuration.
*/
struct LexerConfig {
std::string statement_open {"{%"};
std::string statement_open_no_lstrip {"{%+"};
std::string statement_open_force_lstrip {"{%-"};
std::string statement_close {"%}"};
std::string statement_close_force_rstrip {"-%}"};
std::string line_statement {"##"};
std::string expression_open {"{{"};
std::string expression_open_force_lstrip {"{{-"};
std::string expression_close {"}}"};
std::string expression_close_force_rstrip {"-}}"};
std::string comment_open {"{#"};
std::string comment_open_force_lstrip {"{#-"};
std::string comment_close {"#}"};
std::string comment_close_force_rstrip {"-#}"};
std::string open_chars {"#{"};
bool trim_blocks {false};
bool lstrip_blocks {false};
void update_open_chars() {
open_chars = "";
if (open_chars.find(line_statement[0]) == std::string::npos) {
open_chars += line_statement[0];
}
if (open_chars.find(statement_open[0]) == std::string::npos) {
open_chars += statement_open[0];
}
if (open_chars.find(statement_open_no_lstrip[0]) == std::string::npos) {
open_chars += statement_open_no_lstrip[0];
}
if (open_chars.find(statement_open_force_lstrip[0]) == std::string::npos) {
open_chars += statement_open_force_lstrip[0];
}
if (open_chars.find(expression_open[0]) == std::string::npos) {
open_chars += expression_open[0];
}
if (open_chars.find(expression_open_force_lstrip[0]) == std::string::npos) {
open_chars += expression_open_force_lstrip[0];
}
if (open_chars.find(comment_open[0]) == std::string::npos) {
open_chars += comment_open[0];
}
if (open_chars.find(comment_open_force_lstrip[0]) == std::string::npos) {
open_chars += comment_open_force_lstrip[0];
}
}
};
/*!
* \brief Class for parser configuration.
*/
struct ParserConfig {
bool search_included_templates_in_files {true};
};
/*!
* \brief Class for render configuration.
*/
struct RenderConfig {
bool throw_at_missing_includes {true};
};
} // namespace inja
#endif // INCLUDE_INJA_CONFIG_HPP_

View File

@ -0,0 +1,228 @@
#ifndef INCLUDE_INJA_ENVIRONMENT_HPP_
#define INCLUDE_INJA_ENVIRONMENT_HPP_
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <nlohmann/json.hpp>
#include "config.hpp"
#include "function_storage.hpp"
#include "parser.hpp"
#include "renderer.hpp"
#include "string_view.hpp"
#include "template.hpp"
#include "utils.hpp"
namespace inja {
using json = nlohmann::json;
/*!
* \brief Class for changing the configuration.
*/
class Environment {
std::string input_path;
std::string output_path;
LexerConfig lexer_config;
ParserConfig parser_config;
RenderConfig render_config;
FunctionStorage function_storage;
TemplateStorage template_storage;
public:
Environment() : Environment("") {}
explicit Environment(const std::string &global_path) : input_path(global_path), output_path(global_path) {}
Environment(const std::string &input_path, const std::string &output_path)
: input_path(input_path), output_path(output_path) {}
/// Sets the opener and closer for template statements
void set_statement(const std::string &open, const std::string &close) {
lexer_config.statement_open = open;
lexer_config.statement_open_no_lstrip = open + "+";
lexer_config.statement_open_force_lstrip = open + "-";
lexer_config.statement_close = close;
lexer_config.statement_close_force_rstrip = "-" + close;
lexer_config.update_open_chars();
}
/// Sets the opener for template line statements
void set_line_statement(const std::string &open) {
lexer_config.line_statement = open;
lexer_config.update_open_chars();
}
/// Sets the opener and closer for template expressions
void set_expression(const std::string &open, const std::string &close) {
lexer_config.expression_open = open;
lexer_config.expression_open_force_lstrip = open + "-";
lexer_config.expression_close = close;
lexer_config.expression_close_force_rstrip = "-" + close;
lexer_config.update_open_chars();
}
/// Sets the opener and closer for template comments
void set_comment(const std::string &open, const std::string &close) {
lexer_config.comment_open = open;
lexer_config.comment_open_force_lstrip = open + "-";
lexer_config.comment_close = close;
lexer_config.comment_close_force_rstrip = "-" + close;
lexer_config.update_open_chars();
}
/// Sets whether to remove the first newline after a block
void set_trim_blocks(bool trim_blocks) {
lexer_config.trim_blocks = trim_blocks;
}
/// Sets whether to strip the spaces and tabs from the start of a line to a block
void set_lstrip_blocks(bool lstrip_blocks) {
lexer_config.lstrip_blocks = lstrip_blocks;
}
/// Sets the element notation syntax
void set_search_included_templates_in_files(bool search_in_files) {
parser_config.search_included_templates_in_files = search_in_files;
}
/// Sets whether a missing include will throw an error
void set_throw_at_missing_includes(bool will_throw) {
render_config.throw_at_missing_includes = will_throw;
}
Template parse(nonstd::string_view input) {
Parser parser(parser_config, lexer_config, template_storage, function_storage);
return parser.parse(input);
}
Template parse_template(const std::string &filename) {
Parser parser(parser_config, lexer_config, template_storage, function_storage);
auto result = Template(parser.load_file(input_path + static_cast<std::string>(filename)));
parser.parse_into_template(result, input_path + static_cast<std::string>(filename));
return result;
}
Template parse_file(const std::string &filename) {
return parse_template(filename);
}
std::string render(nonstd::string_view input, const json &data) { return render(parse(input), data); }
std::string render(const Template &tmpl, const json &data) {
std::stringstream os;
render_to(os, tmpl, data);
return os.str();
}
std::string render_file(const std::string &filename, const json &data) {
return render(parse_template(filename), data);
}
std::string render_file_with_json_file(const std::string &filename, const std::string &filename_data) {
const json data = load_json(filename_data);
return render_file(filename, data);
}
void write(const std::string &filename, const json &data, const std::string &filename_out) {
std::ofstream file(output_path + filename_out);
file << render_file(filename, data);
file.close();
}
void write(const Template &temp, const json &data, const std::string &filename_out) {
std::ofstream file(output_path + filename_out);
file << render(temp, data);
file.close();
}
void write_with_json_file(const std::string &filename, const std::string &filename_data,
const std::string &filename_out) {
const json data = load_json(filename_data);
write(filename, data, filename_out);
}
void write_with_json_file(const Template &temp, const std::string &filename_data, const std::string &filename_out) {
const json data = load_json(filename_data);
write(temp, data, filename_out);
}
std::ostream &render_to(std::ostream &os, const Template &tmpl, const json &data) {
Renderer(render_config, template_storage, function_storage).render_to(os, tmpl, data);
return os;
}
std::string load_file(const std::string &filename) {
Parser parser(parser_config, lexer_config, template_storage, function_storage);
return parser.load_file(input_path + filename);
}
json load_json(const std::string &filename) {
std::ifstream file;
open_file_or_throw(input_path + filename, file);
json j;
file >> j;
return j;
}
/*!
@brief Adds a variadic callback
*/
void add_callback(const std::string &name, const CallbackFunction &callback) {
add_callback(name, -1, callback);
}
/*!
@brief Adds a variadic void callback
*/
void add_void_callback(const std::string &name, const VoidCallbackFunction &callback) {
add_void_callback(name, -1, callback);
}
/*!
@brief Adds a callback with given number or arguments
*/
void add_callback(const std::string &name, int num_args, const CallbackFunction &callback) {
function_storage.add_callback(name, num_args, callback);
}
/*!
@brief Adds a void callback with given number or arguments
*/
void add_void_callback(const std::string &name, int num_args, const VoidCallbackFunction &callback) {
function_storage.add_callback(name, num_args, [callback](Arguments& args) { callback(args); return json(); });
}
/** Includes a template with a given name into the environment.
* Then, a template can be rendered in another template using the
* include "<name>" syntax.
*/
void include_template(const std::string &name, const Template &tmpl) {
template_storage[name] = tmpl;
}
};
/*!
@brief render with default settings to a string
*/
inline std::string render(nonstd::string_view input, const json &data) {
return Environment().render(input, data);
}
/*!
@brief render with default settings to the given output stream
*/
inline void render_to(std::ostream &os, nonstd::string_view input, const json &data) {
Environment env;
env.render_to(os, env.parse(input), data);
}
} // namespace inja
#endif // INCLUDE_INJA_ENVIRONMENT_HPP_

View File

@ -0,0 +1,48 @@
#ifndef INCLUDE_INJA_EXCEPTIONS_HPP_
#define INCLUDE_INJA_EXCEPTIONS_HPP_
#include <stdexcept>
#include <string>
namespace inja {
struct SourceLocation {
size_t line;
size_t column;
};
struct InjaError : public std::runtime_error {
const std::string type;
const std::string message;
const SourceLocation location;
explicit InjaError(const std::string &type, const std::string &message)
: std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message), location({0, 0}) {}
explicit InjaError(const std::string &type, const std::string &message, SourceLocation location)
: std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" +
std::to_string(location.column) + ") " + message),
type(type), message(message), location(location) {}
};
struct ParserError : public InjaError {
explicit ParserError(const std::string &message, SourceLocation location) : InjaError("parser_error", message, location) {}
};
struct RenderError : public InjaError {
explicit RenderError(const std::string &message, SourceLocation location) : InjaError("render_error", message, location) {}
};
struct FileError : public InjaError {
explicit FileError(const std::string &message) : InjaError("file_error", message) {}
explicit FileError(const std::string &message, SourceLocation location) : InjaError("file_error", message, location) {}
};
struct JsonError : public InjaError {
explicit JsonError(const std::string &message, SourceLocation location) : InjaError("json_error", message, location) {}
};
} // namespace inja
#endif // INCLUDE_INJA_EXCEPTIONS_HPP_

View File

@ -0,0 +1,142 @@
#ifndef INCLUDE_INJA_FUNCTION_STORAGE_HPP_
#define INCLUDE_INJA_FUNCTION_STORAGE_HPP_
#include <vector>
#include "string_view.hpp"
namespace inja {
using json = nlohmann::json;
using Arguments = std::vector<const json *>;
using CallbackFunction = std::function<json(Arguments &args)>;
using VoidCallbackFunction = std::function<void(Arguments &args)>;
/*!
* \brief Class for builtin functions and user-defined callbacks.
*/
class FunctionStorage {
public:
enum class Operation {
Not,
And,
Or,
In,
Equal,
NotEqual,
Greater,
GreaterEqual,
Less,
LessEqual,
Add,
Subtract,
Multiplication,
Division,
Power,
Modulo,
AtId,
At,
Default,
DivisibleBy,
Even,
Exists,
ExistsInObject,
First,
Float,
Int,
IsArray,
IsBoolean,
IsFloat,
IsInteger,
IsNumber,
IsObject,
IsString,
Last,
Length,
Lower,
Max,
Min,
Odd,
Range,
Round,
Sort,
Upper,
Super,
Join,
Callback,
ParenLeft,
ParenRight,
None,
};
struct FunctionData {
explicit FunctionData(const Operation &op, const CallbackFunction &cb = CallbackFunction{}) : operation(op), callback(cb) {}
const Operation operation;
const CallbackFunction callback;
};
private:
const int VARIADIC {-1};
std::map<std::pair<std::string, int>, FunctionData> function_storage = {
{std::make_pair("at", 2), FunctionData { Operation::At }},
{std::make_pair("default", 2), FunctionData { Operation::Default }},
{std::make_pair("divisibleBy", 2), FunctionData { Operation::DivisibleBy }},
{std::make_pair("even", 1), FunctionData { Operation::Even }},
{std::make_pair("exists", 1), FunctionData { Operation::Exists }},
{std::make_pair("existsIn", 2), FunctionData { Operation::ExistsInObject }},
{std::make_pair("first", 1), FunctionData { Operation::First }},
{std::make_pair("float", 1), FunctionData { Operation::Float }},
{std::make_pair("int", 1), FunctionData { Operation::Int }},
{std::make_pair("isArray", 1), FunctionData { Operation::IsArray }},
{std::make_pair("isBoolean", 1), FunctionData { Operation::IsBoolean }},
{std::make_pair("isFloat", 1), FunctionData { Operation::IsFloat }},
{std::make_pair("isInteger", 1), FunctionData { Operation::IsInteger }},
{std::make_pair("isNumber", 1), FunctionData { Operation::IsNumber }},
{std::make_pair("isObject", 1), FunctionData { Operation::IsObject }},
{std::make_pair("isString", 1), FunctionData { Operation::IsString }},
{std::make_pair("last", 1), FunctionData { Operation::Last }},
{std::make_pair("length", 1), FunctionData { Operation::Length }},
{std::make_pair("lower", 1), FunctionData { Operation::Lower }},
{std::make_pair("max", 1), FunctionData { Operation::Max }},
{std::make_pair("min", 1), FunctionData { Operation::Min }},
{std::make_pair("odd", 1), FunctionData { Operation::Odd }},
{std::make_pair("range", 1), FunctionData { Operation::Range }},
{std::make_pair("round", 2), FunctionData { Operation::Round }},
{std::make_pair("sort", 1), FunctionData { Operation::Sort }},
{std::make_pair("upper", 1), FunctionData { Operation::Upper }},
{std::make_pair("super", 0), FunctionData { Operation::Super }},
{std::make_pair("super", 1), FunctionData { Operation::Super }},
{std::make_pair("join", 2), FunctionData { Operation::Join }},
};
public:
void add_builtin(nonstd::string_view name, int num_args, Operation op) {
function_storage.emplace(std::make_pair(static_cast<std::string>(name), num_args), FunctionData { op });
}
void add_callback(nonstd::string_view name, int num_args, const CallbackFunction &callback) {
function_storage.emplace(std::make_pair(static_cast<std::string>(name), num_args), FunctionData { Operation::Callback, callback });
}
FunctionData find_function(nonstd::string_view name, int num_args) const {
auto it = function_storage.find(std::make_pair(static_cast<std::string>(name), num_args));
if (it != function_storage.end()) {
return it->second;
// Find variadic function
} else if (num_args > 0) {
it = function_storage.find(std::make_pair(static_cast<std::string>(name), VARIADIC));
if (it != function_storage.end()) {
return it->second;
}
}
return FunctionData { Operation::None };
}
};
} // namespace inja
#endif // INCLUDE_INJA_FUNCTION_STORAGE_HPP_

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@
#include <sstream>
using std::string; using std::to_string;
#include <inja.hpp>
#include "inja.hpp"
using namespace inja;
using json = nlohmann::json;
@ -132,6 +132,41 @@ int main(int argc, char *argv[])
std::cout << args.at(0)->get<int>() << std::endl;
});
env.add_callback("fmtInt", 3, [](Arguments& args) {
int num = args.at(0)->get<int>();
int ndigits = args.at(1)->get<int>();
int format = args.at(2)->get<int>();
char buf[ndigits + 1];
bool printing_zeros = (format == 2);
int pow10 = 1;
for (int i = 1; i < ndigits; i++) {
pow10 *= 10;
}
char *ptr = buf;
while (pow10 > 0) {
div_t div_result = div(num, pow10);
num = div_result.rem;
pow10 /= 10;
if (div_result.quot != 0) {
printing_zeros = true;
} else if (!printing_zeros) {
if (pow10 == 0) {
printing_zeros = true;
} else {
if (format == 1) {
*ptr++ = ' ';
}
continue;
}
}
if (div_result.quot != 0 || printing_zeros) {
*ptr++ = '0' + div_result.quot;
}
}
*ptr = 0;
return string{buf};
});
try
{
env.write_with_json_file(templateFilepath, jsonfilepath, outputFilepath);

434
tools/jsonproc/lexer.hpp Normal file
View File

@ -0,0 +1,434 @@
#ifndef INCLUDE_INJA_LEXER_HPP_
#define INCLUDE_INJA_LEXER_HPP_
#include <cctype>
#include <locale>
#include "config.hpp"
#include "token.hpp"
#include "utils.hpp"
namespace inja {
/*!
* \brief Class for lexing an inja Template.
*/
class Lexer {
enum class State {
Text,
ExpressionStart,
ExpressionStartForceLstrip,
ExpressionBody,
LineStart,
LineBody,
StatementStart,
StatementStartNoLstrip,
StatementStartForceLstrip,
StatementBody,
CommentStart,
CommentStartForceLstrip,
CommentBody,
};
enum class MinusState {
Operator,
Number,
};
const LexerConfig &config;
State state;
MinusState minus_state;
nonstd::string_view m_in;
size_t tok_start;
size_t pos;
Token scan_body(nonstd::string_view close, Token::Kind closeKind, nonstd::string_view close_trim = nonstd::string_view(), bool trim = false) {
again:
// skip whitespace (except for \n as it might be a close)
if (tok_start >= m_in.size()) {
return make_token(Token::Kind::Eof);
}
const char ch = m_in[tok_start];
if (ch == ' ' || ch == '\t' || ch == '\r') {
tok_start += 1;
goto again;
}
// check for close
if (!close_trim.empty() && inja::string_view::starts_with(m_in.substr(tok_start), close_trim)) {
state = State::Text;
pos = tok_start + close_trim.size();
const Token tok = make_token(closeKind);
skip_whitespaces_and_newlines();
return tok;
}
if (inja::string_view::starts_with(m_in.substr(tok_start), close)) {
state = State::Text;
pos = tok_start + close.size();
const Token tok = make_token(closeKind);
if (trim) {
skip_whitespaces_and_first_newline();
}
return tok;
}
// skip \n
if (ch == '\n') {
tok_start += 1;
goto again;
}
pos = tok_start + 1;
if (std::isalpha(ch)) {
minus_state = MinusState::Operator;
return scan_id();
}
const MinusState current_minus_state = minus_state;
if (minus_state == MinusState::Operator) {
minus_state = MinusState::Number;
}
switch (ch) {
case '+':
return make_token(Token::Kind::Plus);
case '-':
if (current_minus_state == MinusState::Operator) {
return make_token(Token::Kind::Minus);
}
return scan_number();
case '*':
return make_token(Token::Kind::Times);
case '/':
return make_token(Token::Kind::Slash);
case '^':
return make_token(Token::Kind::Power);
case '%':
return make_token(Token::Kind::Percent);
case '.':
return make_token(Token::Kind::Dot);
case ',':
return make_token(Token::Kind::Comma);
case ':':
return make_token(Token::Kind::Colon);
case '(':
return make_token(Token::Kind::LeftParen);
case ')':
minus_state = MinusState::Operator;
return make_token(Token::Kind::RightParen);
case '[':
return make_token(Token::Kind::LeftBracket);
case ']':
minus_state = MinusState::Operator;
return make_token(Token::Kind::RightBracket);
case '{':
return make_token(Token::Kind::LeftBrace);
case '}':
minus_state = MinusState::Operator;
return make_token(Token::Kind::RightBrace);
case '>':
if (pos < m_in.size() && m_in[pos] == '=') {
pos += 1;
return make_token(Token::Kind::GreaterEqual);
}
return make_token(Token::Kind::GreaterThan);
case '<':
if (pos < m_in.size() && m_in[pos] == '=') {
pos += 1;
return make_token(Token::Kind::LessEqual);
}
return make_token(Token::Kind::LessThan);
case '=':
if (pos < m_in.size() && m_in[pos] == '=') {
pos += 1;
return make_token(Token::Kind::Equal);
}
return make_token(Token::Kind::Unknown);
case '!':
if (pos < m_in.size() && m_in[pos] == '=') {
pos += 1;
return make_token(Token::Kind::NotEqual);
}
return make_token(Token::Kind::Unknown);
case '\"':
return scan_string();
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
minus_state = MinusState::Operator;
return scan_number();
case '_':
case '@':
case '$':
minus_state = MinusState::Operator;
return scan_id();
default:
return make_token(Token::Kind::Unknown);
}
}
Token scan_id() {
for (;;) {
if (pos >= m_in.size()) {
break;
}
const char ch = m_in[pos];
if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-') {
break;
}
pos += 1;
}
return make_token(Token::Kind::Id);
}
Token scan_number() {
for (;;) {
if (pos >= m_in.size()) {
break;
}
const char ch = m_in[pos];
// be very permissive in lexer (we'll catch errors when conversion happens)
if (!std::isdigit(ch) && ch != '.' && ch != 'e' && ch != 'E' && ch != '+' && ch != '-') {
break;
}
pos += 1;
}
return make_token(Token::Kind::Number);
}
Token scan_string() {
bool escape {false};
for (;;) {
if (pos >= m_in.size()) {
break;
}
const char ch = m_in[pos++];
if (ch == '\\') {
escape = true;
} else if (!escape && ch == m_in[tok_start]) {
break;
} else {
escape = false;
}
}
return make_token(Token::Kind::String);
}
Token make_token(Token::Kind kind) const { return Token(kind, string_view::slice(m_in, tok_start, pos)); }
void skip_whitespaces_and_newlines() {
if (pos < m_in.size()) {
while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t' || m_in[pos] == '\n' || m_in[pos] == '\r')) {
pos += 1;
}
}
}
void skip_whitespaces_and_first_newline() {
if (pos < m_in.size()) {
while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t')) {
pos += 1;
}
}
if (pos < m_in.size()) {
const char ch = m_in[pos];
if (ch == '\n') {
pos += 1;
} else if (ch == '\r') {
pos += 1;
if (pos < m_in.size() && m_in[pos] == '\n') {
pos += 1;
}
}
}
}
static nonstd::string_view clear_final_line_if_whitespace(nonstd::string_view text) {
nonstd::string_view result = text;
while (!result.empty()) {
const char ch = result.back();
if (ch == ' ' || ch == '\t') {
result.remove_suffix(1);
} else if (ch == '\n' || ch == '\r') {
break;
} else {
return text;
}
}
return result;
}
public:
explicit Lexer(const LexerConfig &config) : config(config), state(State::Text), minus_state(MinusState::Number) {}
SourceLocation current_position() const {
return get_source_location(m_in, tok_start);
}
void start(nonstd::string_view input) {
m_in = input;
tok_start = 0;
pos = 0;
state = State::Text;
minus_state = MinusState::Number;
// Consume byte order mark (BOM) for UTF-8
if (inja::string_view::starts_with(m_in, "\xEF\xBB\xBF")) {
m_in = m_in.substr(3);
}
}
Token scan() {
tok_start = pos;
again:
if (tok_start >= m_in.size()) {
return make_token(Token::Kind::Eof);
}
switch (state) {
default:
case State::Text: {
// fast-scan to first open character
const size_t open_start = m_in.substr(pos).find_first_of(config.open_chars);
if (open_start == nonstd::string_view::npos) {
// didn't find open, return remaining text as text token
pos = m_in.size();
return make_token(Token::Kind::Text);
}
pos += open_start;
// try to match one of the opening sequences, and get the close
nonstd::string_view open_str = m_in.substr(pos);
bool must_lstrip = false;
if (inja::string_view::starts_with(open_str, config.expression_open)) {
if (inja::string_view::starts_with(open_str, config.expression_open_force_lstrip)) {
state = State::ExpressionStartForceLstrip;
must_lstrip = true;
} else {
state = State::ExpressionStart;
}
} else if (inja::string_view::starts_with(open_str, config.statement_open)) {
if (inja::string_view::starts_with(open_str, config.statement_open_no_lstrip)) {
state = State::StatementStartNoLstrip;
} else if (inja::string_view::starts_with(open_str, config.statement_open_force_lstrip )) {
state = State::StatementStartForceLstrip;
must_lstrip = true;
} else {
state = State::StatementStart;
must_lstrip = config.lstrip_blocks;
}
} else if (inja::string_view::starts_with(open_str, config.comment_open)) {
if (inja::string_view::starts_with(open_str, config.comment_open_force_lstrip)) {
state = State::CommentStartForceLstrip;
must_lstrip = true;
} else {
state = State::CommentStart;
must_lstrip = config.lstrip_blocks;
}
} else if ((pos == 0 || m_in[pos - 1] == '\n') && inja::string_view::starts_with(open_str, config.line_statement)) {
state = State::LineStart;
} else {
pos += 1; // wasn't actually an opening sequence
goto again;
}
nonstd::string_view text = string_view::slice(m_in, tok_start, pos);
if (must_lstrip) {
text = clear_final_line_if_whitespace(text);
}
if (text.empty()) {
goto again; // don't generate empty token
}
return Token(Token::Kind::Text, text);
}
case State::ExpressionStart: {
state = State::ExpressionBody;
pos += config.expression_open.size();
return make_token(Token::Kind::ExpressionOpen);
}
case State::ExpressionStartForceLstrip: {
state = State::ExpressionBody;
pos += config.expression_open_force_lstrip.size();
return make_token(Token::Kind::ExpressionOpen);
}
case State::LineStart: {
state = State::LineBody;
pos += config.line_statement.size();
return make_token(Token::Kind::LineStatementOpen);
}
case State::StatementStart: {
state = State::StatementBody;
pos += config.statement_open.size();
return make_token(Token::Kind::StatementOpen);
}
case State::StatementStartNoLstrip: {
state = State::StatementBody;
pos += config.statement_open_no_lstrip.size();
return make_token(Token::Kind::StatementOpen);
}
case State::StatementStartForceLstrip: {
state = State::StatementBody;
pos += config.statement_open_force_lstrip.size();
return make_token(Token::Kind::StatementOpen);
}
case State::CommentStart: {
state = State::CommentBody;
pos += config.comment_open.size();
return make_token(Token::Kind::CommentOpen);
}
case State::CommentStartForceLstrip: {
state = State::CommentBody;
pos += config.comment_open_force_lstrip.size();
return make_token(Token::Kind::CommentOpen);
}
case State::ExpressionBody:
return scan_body(config.expression_close, Token::Kind::ExpressionClose, config.expression_close_force_rstrip);
case State::LineBody:
return scan_body("\n", Token::Kind::LineStatementClose);
case State::StatementBody:
return scan_body(config.statement_close, Token::Kind::StatementClose, config.statement_close_force_rstrip, config.trim_blocks);
case State::CommentBody: {
// fast-scan to comment close
const size_t end = m_in.substr(pos).find(config.comment_close);
if (end == nonstd::string_view::npos) {
pos = m_in.size();
return make_token(Token::Kind::Eof);
}
// Check for trim pattern
const bool must_rstrip = inja::string_view::starts_with(m_in.substr(pos + end - 1), config.comment_close_force_rstrip);
// return the entire comment in the close token
state = State::Text;
pos += end + config.comment_close.size();
Token tok = make_token(Token::Kind::CommentClose);
if (must_rstrip || config.trim_blocks) {
skip_whitespaces_and_first_newline();
}
return tok;
}
}
}
const LexerConfig &get_config() const {
return config;
}
};
} // namespace inja
#endif // INCLUDE_INJA_LEXER_HPP_

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013-2017 Niels Lohmann
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.

File diff suppressed because it is too large Load Diff

374
tools/jsonproc/node.hpp Normal file
View File

@ -0,0 +1,374 @@
#ifndef INCLUDE_INJA_NODE_HPP_
#define INCLUDE_INJA_NODE_HPP_
#include <string>
#include <utility>
#include <nlohmann/json.hpp>
#include "function_storage.hpp"
#include "string_view.hpp"
namespace inja {
class NodeVisitor;
class BlockNode;
class TextNode;
class ExpressionNode;
class LiteralNode;
class JsonNode;
class FunctionNode;
class ExpressionListNode;
class StatementNode;
class ForStatementNode;
class ForArrayStatementNode;
class ForObjectStatementNode;
class IfStatementNode;
class IncludeStatementNode;
class ExtendsStatementNode;
class BlockStatementNode;
class SetStatementNode;
class NodeVisitor {
public:
virtual ~NodeVisitor() = default;
virtual void visit(const BlockNode& node) = 0;
virtual void visit(const TextNode& node) = 0;
virtual void visit(const ExpressionNode& node) = 0;
virtual void visit(const LiteralNode& node) = 0;
virtual void visit(const JsonNode& node) = 0;
virtual void visit(const FunctionNode& node) = 0;
virtual void visit(const ExpressionListNode& node) = 0;
virtual void visit(const StatementNode& node) = 0;
virtual void visit(const ForStatementNode& node) = 0;
virtual void visit(const ForArrayStatementNode& node) = 0;
virtual void visit(const ForObjectStatementNode& node) = 0;
virtual void visit(const IfStatementNode& node) = 0;
virtual void visit(const IncludeStatementNode& node) = 0;
virtual void visit(const ExtendsStatementNode& node) = 0;
virtual void visit(const BlockStatementNode& node) = 0;
virtual void visit(const SetStatementNode& node) = 0;
};
/*!
* \brief Base node class for the abstract syntax tree (AST).
*/
class AstNode {
public:
virtual void accept(NodeVisitor& v) const = 0;
size_t pos;
AstNode(size_t pos) : pos(pos) { }
virtual ~AstNode() { }
};
class BlockNode : public AstNode {
public:
std::vector<std::shared_ptr<AstNode>> nodes;
explicit BlockNode() : AstNode(0) {}
void accept(NodeVisitor& v) const {
v.visit(*this);
}
};
class TextNode : public AstNode {
public:
const size_t length;
explicit TextNode(size_t pos, size_t length): AstNode(pos), length(length) { }
void accept(NodeVisitor& v) const {
v.visit(*this);
}
};
class ExpressionNode : public AstNode {
public:
explicit ExpressionNode(size_t pos) : AstNode(pos) {}
void accept(NodeVisitor& v) const {
v.visit(*this);
}
};
class LiteralNode : public ExpressionNode {
public:
const nlohmann::json value;
explicit LiteralNode(const nlohmann::json& value, size_t pos) : ExpressionNode(pos), value(value) { }
void accept(NodeVisitor& v) const {
v.visit(*this);
}
};
class JsonNode : public ExpressionNode {
public:
const std::string name;
const json::json_pointer ptr;
static std::string convert_dot_to_json_ptr(nonstd::string_view ptr_name) {
std::string result;
do {
nonstd::string_view part;
std::tie(part, ptr_name) = string_view::split(ptr_name, '.');
result.push_back('/');
result.append(part.begin(), part.end());
} while (!ptr_name.empty());
return result;
}
explicit JsonNode(nonstd::string_view ptr_name, size_t pos) : ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_json_ptr(ptr_name))) { }
void accept(NodeVisitor& v) const {
v.visit(*this);
}
};
class FunctionNode : public ExpressionNode {
using Op = FunctionStorage::Operation;
public:
enum class Associativity {
Left,
Right,
};
unsigned int precedence;
Associativity associativity;
Op operation;
std::string name;
int number_args; // Should also be negative -> -1 for unknown number
std::vector<std::shared_ptr<ExpressionNode>> arguments;
CallbackFunction callback;
explicit FunctionNode(nonstd::string_view name, size_t pos) : ExpressionNode(pos), precedence(8), associativity(Associativity::Left), operation(Op::Callback), name(name), number_args(1) { }
explicit FunctionNode(Op operation, size_t pos) : ExpressionNode(pos), operation(operation), number_args(1) {
switch (operation) {
case Op::Not: {
number_args = 1;
precedence = 4;
associativity = Associativity::Left;
} break;
case Op::And: {
number_args = 2;
precedence = 1;
associativity = Associativity::Left;
} break;
case Op::Or: {
number_args = 2;
precedence = 1;
associativity = Associativity::Left;
} break;
case Op::In: {
number_args = 2;
precedence = 2;
associativity = Associativity::Left;
} break;
case Op::Equal: {
number_args = 2;
precedence = 2;
associativity = Associativity::Left;
} break;
case Op::NotEqual: {
number_args = 2;
precedence = 2;
associativity = Associativity::Left;
} break;
case Op::Greater: {
number_args = 2;
precedence = 2;
associativity = Associativity::Left;
} break;
case Op::GreaterEqual: {
number_args = 2;
precedence = 2;
associativity = Associativity::Left;
} break;
case Op::Less: {
number_args = 2;
precedence = 2;
associativity = Associativity::Left;
} break;
case Op::LessEqual: {
number_args = 2;
precedence = 2;
associativity = Associativity::Left;
} break;
case Op::Add: {
number_args = 2;
precedence = 3;
associativity = Associativity::Left;
} break;
case Op::Subtract: {
number_args = 2;
precedence = 3;
associativity = Associativity::Left;
} break;
case Op::Multiplication: {
number_args = 2;
precedence = 4;
associativity = Associativity::Left;
} break;
case Op::Division: {
number_args = 2;
precedence = 4;
associativity = Associativity::Left;
} break;
case Op::Power: {
number_args = 2;
precedence = 5;
associativity = Associativity::Right;
} break;
case Op::Modulo: {
number_args = 2;
precedence = 4;
associativity = Associativity::Left;
} break;
case Op::AtId: {
number_args = 2;
precedence = 8;
associativity = Associativity::Left;
} break;
default: {
precedence = 1;
associativity = Associativity::Left;
}
}
}
void accept(NodeVisitor& v) const {
v.visit(*this);
}
};
class ExpressionListNode : public AstNode {
public:
std::shared_ptr<ExpressionNode> root;
explicit ExpressionListNode() : AstNode(0) { }
explicit ExpressionListNode(size_t pos) : AstNode(pos) { }
void accept(NodeVisitor& v) const {
v.visit(*this);
}
};
class StatementNode : public AstNode {
public:
StatementNode(size_t pos) : AstNode(pos) { }
virtual void accept(NodeVisitor& v) const = 0;
};
class ForStatementNode : public StatementNode {
public:
ExpressionListNode condition;
BlockNode body;
BlockNode *const parent;
ForStatementNode(BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent) { }
virtual void accept(NodeVisitor& v) const = 0;
};
class ForArrayStatementNode : public ForStatementNode {
public:
const std::string value;
explicit ForArrayStatementNode(const std::string& value, BlockNode *const parent, size_t pos) : ForStatementNode(parent, pos), value(value) { }
void accept(NodeVisitor& v) const {
v.visit(*this);
}
};
class ForObjectStatementNode : public ForStatementNode {
public:
const std::string key;
const std::string value;
explicit ForObjectStatementNode(const std::string& key, const std::string& value, BlockNode *const parent, size_t pos) : ForStatementNode(parent, pos), key(key), value(value) { }
void accept(NodeVisitor& v) const {
v.visit(*this);
}
};
class IfStatementNode : public StatementNode {
public:
ExpressionListNode condition;
BlockNode true_statement;
BlockNode false_statement;
BlockNode *const parent;
const bool is_nested;
bool has_false_statement {false};
explicit IfStatementNode(BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent), is_nested(false) { }
explicit IfStatementNode(bool is_nested, BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent), is_nested(is_nested) { }
void accept(NodeVisitor& v) const {
v.visit(*this);
}
};
class IncludeStatementNode : public StatementNode {
public:
const std::string file;
explicit IncludeStatementNode(const std::string& file, size_t pos) : StatementNode(pos), file(file) { }
void accept(NodeVisitor& v) const {
v.visit(*this);
}
};
class ExtendsStatementNode : public StatementNode {
public:
const std::string file;
explicit ExtendsStatementNode(const std::string& file, size_t pos) : StatementNode(pos), file(file) { }
void accept(NodeVisitor& v) const {
v.visit(*this);
};
};
class BlockStatementNode : public StatementNode {
public:
const std::string name;
BlockNode block;
BlockNode *const parent;
explicit BlockStatementNode(BlockNode *const parent, const std::string& name, size_t pos) : StatementNode(pos), name(name), parent(parent) { }
void accept(NodeVisitor& v) const {
v.visit(*this);
};
};
class SetStatementNode : public StatementNode {
public:
const std::string key;
ExpressionListNode expression;
explicit SetStatementNode(const std::string& key, size_t pos) : StatementNode(pos), key(key) { }
void accept(NodeVisitor& v) const {
v.visit(*this);
}
};
} // namespace inja
#endif // INCLUDE_INJA_NODE_HPP_

646
tools/jsonproc/parser.hpp Normal file
View File

@ -0,0 +1,646 @@
#ifndef INCLUDE_INJA_PARSER_HPP_
#define INCLUDE_INJA_PARSER_HPP_
#include <limits>
#include <stack>
#include <string>
#include <utility>
#include <queue>
#include <vector>
#include "config.hpp"
#include "exceptions.hpp"
#include "function_storage.hpp"
#include "lexer.hpp"
#include "node.hpp"
#include "template.hpp"
#include "token.hpp"
#include "utils.hpp"
#include <nlohmann/json.hpp>
namespace inja {
/*!
* \brief Class for parsing an inja Template.
*/
class Parser {
const ParserConfig &config;
Lexer lexer;
TemplateStorage &template_storage;
const FunctionStorage &function_storage;
Token tok, peek_tok;
bool have_peek_tok {false};
size_t current_paren_level {0};
size_t current_bracket_level {0};
size_t current_brace_level {0};
nonstd::string_view json_literal_start;
BlockNode *current_block {nullptr};
ExpressionListNode *current_expression_list {nullptr};
std::stack<std::pair<FunctionNode*, size_t>> function_stack;
std::vector<std::shared_ptr<ExpressionNode>> arguments;
std::stack<std::shared_ptr<FunctionNode>> operator_stack;
std::stack<IfStatementNode*> if_statement_stack;
std::stack<ForStatementNode*> for_statement_stack;
std::stack<BlockStatementNode*> block_statement_stack;
inline void throw_parser_error(const std::string &message) {
INJA_THROW(ParserError(message, lexer.current_position()));
}
inline void get_next_token() {
if (have_peek_tok) {
tok = peek_tok;
have_peek_tok = false;
} else {
tok = lexer.scan();
}
}
inline void get_peek_token() {
if (!have_peek_tok) {
peek_tok = lexer.scan();
have_peek_tok = true;
}
}
inline void add_json_literal(const char* content_ptr) {
nonstd::string_view json_text(json_literal_start.data(), tok.text.data() - json_literal_start.data() + tok.text.size());
arguments.emplace_back(std::make_shared<LiteralNode>(json::parse(json_text), json_text.data() - content_ptr));
}
inline void add_operator() {
auto function = operator_stack.top();
operator_stack.pop();
for (int i = 0; i < function->number_args; ++i) {
function->arguments.insert(function->arguments.begin(), arguments.back());
arguments.pop_back();
}
arguments.emplace_back(function);
}
void add_to_template_storage(nonstd::string_view path, std::string& template_name) {
if (config.search_included_templates_in_files && template_storage.find(template_name) == template_storage.end()) {
// Build the relative path
template_name = static_cast<std::string>(path) + template_name;
if (template_name.compare(0, 2, "./") == 0) {
template_name.erase(0, 2);
}
if (template_storage.find(template_name) == template_storage.end()) {
auto include_template = Template(load_file(template_name));
template_storage.emplace(template_name, include_template);
parse_into_template(template_storage[template_name], template_name);
}
}
}
bool parse_expression(Template &tmpl, Token::Kind closing) {
while (tok.kind != closing && tok.kind != Token::Kind::Eof) {
// Literals
switch (tok.kind) {
case Token::Kind::String: {
if (current_brace_level == 0 && current_bracket_level == 0) {
json_literal_start = tok.text;
add_json_literal(tmpl.content.c_str());
}
} break;
case Token::Kind::Number: {
if (current_brace_level == 0 && current_bracket_level == 0) {
json_literal_start = tok.text;
add_json_literal(tmpl.content.c_str());
}
} break;
case Token::Kind::LeftBracket: {
if (current_brace_level == 0 && current_bracket_level == 0) {
json_literal_start = tok.text;
}
current_bracket_level += 1;
} break;
case Token::Kind::LeftBrace: {
if (current_brace_level == 0 && current_bracket_level == 0) {
json_literal_start = tok.text;
}
current_brace_level += 1;
} break;
case Token::Kind::RightBracket: {
if (current_bracket_level == 0) {
throw_parser_error("unexpected ']'");
}
current_bracket_level -= 1;
if (current_brace_level == 0 && current_bracket_level == 0) {
add_json_literal(tmpl.content.c_str());
}
} break;
case Token::Kind::RightBrace: {
if (current_brace_level == 0) {
throw_parser_error("unexpected '}'");
}
current_brace_level -= 1;
if (current_brace_level == 0 && current_bracket_level == 0) {
add_json_literal(tmpl.content.c_str());
}
} break;
case Token::Kind::Id: {
get_peek_token();
// Json Literal
if (tok.text == static_cast<decltype(tok.text)>("true") || tok.text == static_cast<decltype(tok.text)>("false") || tok.text == static_cast<decltype(tok.text)>("null")) {
if (current_brace_level == 0 && current_bracket_level == 0) {
json_literal_start = tok.text;
add_json_literal(tmpl.content.c_str());
}
// Operator
} else if (tok.text == "and" || tok.text == "or" || tok.text == "in" || tok.text == "not") {
goto parse_operator;
// Functions
} else if (peek_tok.kind == Token::Kind::LeftParen) {
operator_stack.emplace(std::make_shared<FunctionNode>(static_cast<std::string>(tok.text), tok.text.data() - tmpl.content.c_str()));
function_stack.emplace(operator_stack.top().get(), current_paren_level);
// Variables
} else {
arguments.emplace_back(std::make_shared<JsonNode>(static_cast<std::string>(tok.text), tok.text.data() - tmpl.content.c_str()));
}
// Operators
} break;
case Token::Kind::Equal:
case Token::Kind::NotEqual:
case Token::Kind::GreaterThan:
case Token::Kind::GreaterEqual:
case Token::Kind::LessThan:
case Token::Kind::LessEqual:
case Token::Kind::Plus:
case Token::Kind::Minus:
case Token::Kind::Times:
case Token::Kind::Slash:
case Token::Kind::Power:
case Token::Kind::Percent:
case Token::Kind::Dot: {
parse_operator:
FunctionStorage::Operation operation;
switch (tok.kind) {
case Token::Kind::Id: {
if (tok.text == "and") {
operation = FunctionStorage::Operation::And;
} else if (tok.text == "or") {
operation = FunctionStorage::Operation::Or;
} else if (tok.text == "in") {
operation = FunctionStorage::Operation::In;
} else if (tok.text == "not") {
operation = FunctionStorage::Operation::Not;
} else {
throw_parser_error("unknown operator in parser.");
}
} break;
case Token::Kind::Equal: {
operation = FunctionStorage::Operation::Equal;
} break;
case Token::Kind::NotEqual: {
operation = FunctionStorage::Operation::NotEqual;
} break;
case Token::Kind::GreaterThan: {
operation = FunctionStorage::Operation::Greater;
} break;
case Token::Kind::GreaterEqual: {
operation = FunctionStorage::Operation::GreaterEqual;
} break;
case Token::Kind::LessThan: {
operation = FunctionStorage::Operation::Less;
} break;
case Token::Kind::LessEqual: {
operation = FunctionStorage::Operation::LessEqual;
} break;
case Token::Kind::Plus: {
operation = FunctionStorage::Operation::Add;
} break;
case Token::Kind::Minus: {
operation = FunctionStorage::Operation::Subtract;
} break;
case Token::Kind::Times: {
operation = FunctionStorage::Operation::Multiplication;
} break;
case Token::Kind::Slash: {
operation = FunctionStorage::Operation::Division;
} break;
case Token::Kind::Power: {
operation = FunctionStorage::Operation::Power;
} break;
case Token::Kind::Percent: {
operation = FunctionStorage::Operation::Modulo;
} break;
case Token::Kind::Dot: {
operation = FunctionStorage::Operation::AtId;
} break;
default: {
throw_parser_error("unknown operator in parser.");
}
}
auto function_node = std::make_shared<FunctionNode>(operation, tok.text.data() - tmpl.content.c_str());
while (!operator_stack.empty() && ((operator_stack.top()->precedence > function_node->precedence) || (operator_stack.top()->precedence == function_node->precedence && function_node->associativity == FunctionNode::Associativity::Left)) && (operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft)) {
add_operator();
}
operator_stack.emplace(function_node);
} break;
case Token::Kind::Comma: {
if (current_brace_level == 0 && current_bracket_level == 0) {
if (function_stack.empty()) {
throw_parser_error("unexpected ','");
}
function_stack.top().first->number_args += 1;
}
} break;
case Token::Kind::Colon: {
if (current_brace_level == 0 && current_bracket_level == 0) {
throw_parser_error("unexpected ':'");
}
} break;
case Token::Kind::LeftParen: {
current_paren_level += 1;
operator_stack.emplace(std::make_shared<FunctionNode>(FunctionStorage::Operation::ParenLeft, tok.text.data() - tmpl.content.c_str()));
get_peek_token();
if (peek_tok.kind == Token::Kind::RightParen) {
if (!function_stack.empty() && function_stack.top().second == current_paren_level - 1) {
function_stack.top().first->number_args = 0;
}
}
} break;
case Token::Kind::RightParen: {
current_paren_level -= 1;
while (!operator_stack.empty() && operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft) {
add_operator();
}
if (!operator_stack.empty() && operator_stack.top()->operation == FunctionStorage::Operation::ParenLeft) {
operator_stack.pop();
}
if (!function_stack.empty() && function_stack.top().second == current_paren_level) {
auto func = function_stack.top().first;
auto function_data = function_storage.find_function(func->name, func->number_args);
if (function_data.operation == FunctionStorage::Operation::None) {
throw_parser_error("unknown function " + func->name);
}
func->operation = function_data.operation;
if (function_data.operation == FunctionStorage::Operation::Callback) {
func->callback = function_data.callback;
}
if (operator_stack.empty()) {
throw_parser_error("internal error at function " + func->name);
}
add_operator();
function_stack.pop();
}
}
default:
break;
}
get_next_token();
}
while (!operator_stack.empty()) {
add_operator();
}
if (arguments.size() == 1) {
current_expression_list->root = arguments[0];
arguments = {};
} else if (arguments.size() > 1) {
throw_parser_error("malformed expression");
}
return true;
}
bool parse_statement(Template &tmpl, Token::Kind closing, nonstd::string_view path) {
if (tok.kind != Token::Kind::Id) {
return false;
}
if (tok.text == static_cast<decltype(tok.text)>("if")) {
get_next_token();
auto if_statement_node = std::make_shared<IfStatementNode>(current_block, tok.text.data() - tmpl.content.c_str());
current_block->nodes.emplace_back(if_statement_node);
if_statement_stack.emplace(if_statement_node.get());
current_block = &if_statement_node->true_statement;
current_expression_list = &if_statement_node->condition;
if (!parse_expression(tmpl, closing)) {
return false;
}
} else if (tok.text == static_cast<decltype(tok.text)>("else")) {
if (if_statement_stack.empty()) {
throw_parser_error("else without matching if");
}
auto &if_statement_data = if_statement_stack.top();
get_next_token();
if_statement_data->has_false_statement = true;
current_block = &if_statement_data->false_statement;
// Chained else if
if (tok.kind == Token::Kind::Id && tok.text == static_cast<decltype(tok.text)>("if")) {
get_next_token();
auto if_statement_node = std::make_shared<IfStatementNode>(true, current_block, tok.text.data() - tmpl.content.c_str());
current_block->nodes.emplace_back(if_statement_node);
if_statement_stack.emplace(if_statement_node.get());
current_block = &if_statement_node->true_statement;
current_expression_list = &if_statement_node->condition;
if (!parse_expression(tmpl, closing)) {
return false;
}
}
} else if (tok.text == static_cast<decltype(tok.text)>("endif")) {
if (if_statement_stack.empty()) {
throw_parser_error("endif without matching if");
}
// Nested if statements
while (if_statement_stack.top()->is_nested) {
if_statement_stack.pop();
}
auto &if_statement_data = if_statement_stack.top();
get_next_token();
current_block = if_statement_data->parent;
if_statement_stack.pop();
} else if (tok.text == static_cast<decltype(tok.text)>("block")) {
get_next_token();
if (tok.kind != Token::Kind::Id) {
throw_parser_error("expected block name, got '" + tok.describe() + "'");
}
const std::string block_name = static_cast<std::string>(tok.text);
auto block_statement_node = std::make_shared<BlockStatementNode>(current_block, block_name, tok.text.data() - tmpl.content.c_str());
current_block->nodes.emplace_back(block_statement_node);
block_statement_stack.emplace(block_statement_node.get());
current_block = &block_statement_node->block;
auto success = tmpl.block_storage.emplace(block_name, block_statement_node);
if (!success.second) {
throw_parser_error("block with the name '" + block_name + "' does already exist");
}
get_next_token();
} else if (tok.text == static_cast<decltype(tok.text)>("endblock")) {
if (block_statement_stack.empty()) {
throw_parser_error("endblock without matching block");
}
auto &block_statement_data = block_statement_stack.top();
get_next_token();
current_block = block_statement_data->parent;
block_statement_stack.pop();
} else if (tok.text == static_cast<decltype(tok.text)>("for")) {
get_next_token();
// options: for a in arr; for a, b in obj
if (tok.kind != Token::Kind::Id) {
throw_parser_error("expected id, got '" + tok.describe() + "'");
}
Token value_token = tok;
get_next_token();
// Object type
std::shared_ptr<ForStatementNode> for_statement_node;
if (tok.kind == Token::Kind::Comma) {
get_next_token();
if (tok.kind != Token::Kind::Id) {
throw_parser_error("expected id, got '" + tok.describe() + "'");
}
Token key_token = std::move(value_token);
value_token = tok;
get_next_token();
for_statement_node = std::make_shared<ForObjectStatementNode>(static_cast<std::string>(key_token.text), static_cast<std::string>(value_token.text), current_block, tok.text.data() - tmpl.content.c_str());
// Array type
} else {
for_statement_node = std::make_shared<ForArrayStatementNode>(static_cast<std::string>(value_token.text), current_block, tok.text.data() - tmpl.content.c_str());
}
current_block->nodes.emplace_back(for_statement_node);
for_statement_stack.emplace(for_statement_node.get());
current_block = &for_statement_node->body;
current_expression_list = &for_statement_node->condition;
if (tok.kind != Token::Kind::Id || tok.text != static_cast<decltype(tok.text)>("in")) {
throw_parser_error("expected 'in', got '" + tok.describe() + "'");
}
get_next_token();
if (!parse_expression(tmpl, closing)) {
return false;
}
} else if (tok.text == static_cast<decltype(tok.text)>("endfor")) {
if (for_statement_stack.empty()) {
throw_parser_error("endfor without matching for");
}
auto &for_statement_data = for_statement_stack.top();
get_next_token();
current_block = for_statement_data->parent;
for_statement_stack.pop();
} else if (tok.text == static_cast<decltype(tok.text)>("include")) {
get_next_token();
if (tok.kind != Token::Kind::String) {
throw_parser_error("expected string, got '" + tok.describe() + "'");
}
std::string template_name = json::parse(tok.text).get_ref<const std::string &>();
add_to_template_storage(path, template_name);
current_block->nodes.emplace_back(std::make_shared<IncludeStatementNode>(template_name, tok.text.data() - tmpl.content.c_str()));
get_next_token();
} else if (tok.text == static_cast<decltype(tok.text)>("extends")) {
get_next_token();
if (tok.kind != Token::Kind::String) {
throw_parser_error("expected string, got '" + tok.describe() + "'");
}
std::string template_name = json::parse(tok.text).get_ref<const std::string &>();
add_to_template_storage(path, template_name);
current_block->nodes.emplace_back(std::make_shared<ExtendsStatementNode>(template_name, tok.text.data() - tmpl.content.c_str()));
get_next_token();
} else if (tok.text == static_cast<decltype(tok.text)>("set")) {
get_next_token();
if (tok.kind != Token::Kind::Id) {
throw_parser_error("expected variable name, got '" + tok.describe() + "'");
}
std::string key = static_cast<std::string>(tok.text);
get_next_token();
auto set_statement_node = std::make_shared<SetStatementNode>(key, tok.text.data() - tmpl.content.c_str());
current_block->nodes.emplace_back(set_statement_node);
current_expression_list = &set_statement_node->expression;
if (tok.text != static_cast<decltype(tok.text)>("=")) {
throw_parser_error("expected '=', got '" + tok.describe() + "'");
}
get_next_token();
if (!parse_expression(tmpl, closing)) {
return false;
}
} else {
return false;
}
return true;
}
void parse_into(Template &tmpl, nonstd::string_view path) {
lexer.start(tmpl.content);
current_block = &tmpl.root;
for (;;) {
get_next_token();
switch (tok.kind) {
case Token::Kind::Eof: {
if (!if_statement_stack.empty()) {
throw_parser_error("unmatched if");
}
if (!for_statement_stack.empty()) {
throw_parser_error("unmatched for");
}
} return;
case Token::Kind::Text: {
current_block->nodes.emplace_back(std::make_shared<TextNode>(tok.text.data() - tmpl.content.c_str(), tok.text.size()));
} break;
case Token::Kind::StatementOpen: {
get_next_token();
if (!parse_statement(tmpl, Token::Kind::StatementClose, path)) {
throw_parser_error("expected statement, got '" + tok.describe() + "'");
}
if (tok.kind != Token::Kind::StatementClose) {
throw_parser_error("expected statement close, got '" + tok.describe() + "'");
}
} break;
case Token::Kind::LineStatementOpen: {
get_next_token();
if (!parse_statement(tmpl, Token::Kind::LineStatementClose, path)) {
throw_parser_error("expected statement, got '" + tok.describe() + "'");
}
if (tok.kind != Token::Kind::LineStatementClose && tok.kind != Token::Kind::Eof) {
throw_parser_error("expected line statement close, got '" + tok.describe() + "'");
}
} break;
case Token::Kind::ExpressionOpen: {
get_next_token();
auto expression_list_node = std::make_shared<ExpressionListNode>(tok.text.data() - tmpl.content.c_str());
current_block->nodes.emplace_back(expression_list_node);
current_expression_list = expression_list_node.get();
if (!parse_expression(tmpl, Token::Kind::ExpressionClose)) {
throw_parser_error("expected expression, got '" + tok.describe() + "'");
}
if (tok.kind != Token::Kind::ExpressionClose) {
throw_parser_error("expected expression close, got '" + tok.describe() + "'");
}
} break;
case Token::Kind::CommentOpen: {
get_next_token();
if (tok.kind != Token::Kind::CommentClose) {
throw_parser_error("expected comment close, got '" + tok.describe() + "'");
}
} break;
default: {
throw_parser_error("unexpected token '" + tok.describe() + "'");
} break;
}
}
}
public:
explicit Parser(const ParserConfig &parser_config, const LexerConfig &lexer_config,
TemplateStorage &template_storage, const FunctionStorage &function_storage)
: config(parser_config), lexer(lexer_config), template_storage(template_storage), function_storage(function_storage) { }
Template parse(nonstd::string_view input, nonstd::string_view path) {
auto result = Template(static_cast<std::string>(input));
parse_into(result, path);
return result;
}
Template parse(nonstd::string_view input) {
return parse(input, "./");
}
void parse_into_template(Template& tmpl, nonstd::string_view filename) {
nonstd::string_view path = filename.substr(0, filename.find_last_of("/\\") + 1);
// StringRef path = sys::path::parent_path(filename);
auto sub_parser = Parser(config, lexer.get_config(), template_storage, function_storage);
sub_parser.parse_into(tmpl, path);
}
std::string load_file(nonstd::string_view filename) {
std::ifstream file;
open_file_or_throw(static_cast<std::string>(filename), file);
std::string text((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
return text;
}
};
} // namespace inja
#endif // INCLUDE_INJA_PARSER_HPP_

717
tools/jsonproc/renderer.hpp Normal file
View File

@ -0,0 +1,717 @@
#ifndef INCLUDE_INJA_RENDERER_HPP_
#define INCLUDE_INJA_RENDERER_HPP_
#include <algorithm>
#include <numeric>
#include <string>
#include <utility>
#include <vector>
#include <nlohmann/json.hpp>
#include "config.hpp"
#include "exceptions.hpp"
#include "node.hpp"
#include "template.hpp"
#include "utils.hpp"
namespace inja {
/*!
* \brief Class for rendering a Template with data.
*/
class Renderer : public NodeVisitor {
using Op = FunctionStorage::Operation;
const RenderConfig config;
const TemplateStorage &template_storage;
const FunctionStorage &function_storage;
const Template *current_template;
size_t current_level {0};
std::vector<const Template*> template_stack;
std::vector<const BlockStatementNode*> block_statement_stack;
const json *json_input;
std::ostream *output_stream;
json json_additional_data;
json* current_loop_data = &json_additional_data["loop"];
std::vector<std::shared_ptr<json>> json_tmp_stack;
std::stack<const json*> json_eval_stack;
std::stack<const JsonNode*> not_found_stack;
bool break_rendering {false};
bool truthy(const json* data) const {
if (data->is_boolean()) {
return data->get<bool>();
} else if (data->is_number()) {
return (*data != 0);
} else if (data->is_null()) {
return false;
}
return !data->empty();
}
void print_json(const std::shared_ptr<json> value) {
if (value->is_string()) {
*output_stream << value->get_ref<const json::string_t&>();
} else if (value->is_number_integer()) {
*output_stream << value->get<const json::number_integer_t>();
} else if (value->is_null()) {
} else {
*output_stream << value->dump();
}
}
const std::shared_ptr<json> eval_expression_list(const ExpressionListNode& expression_list) {
if (!expression_list.root) {
throw_renderer_error("empty expression", expression_list);
}
expression_list.root->accept(*this);
if (json_eval_stack.empty()) {
throw_renderer_error("empty expression", expression_list);
} else if (json_eval_stack.size() != 1) {
throw_renderer_error("malformed expression", expression_list);
}
const auto result = json_eval_stack.top();
json_eval_stack.pop();
if (!result) {
if (not_found_stack.empty()) {
throw_renderer_error("expression could not be evaluated", expression_list);
}
auto node = not_found_stack.top();
not_found_stack.pop();
throw_renderer_error("variable '" + static_cast<std::string>(node->name) + "' not found", *node);
}
return std::make_shared<json>(*result);
}
void throw_renderer_error(const std::string &message, const AstNode& node) {
SourceLocation loc = get_source_location(current_template->content, node.pos);
INJA_THROW(RenderError(message, loc));
}
template<size_t N, size_t N_start = 0, bool throw_not_found=true>
std::array<const json*, N> get_arguments(const FunctionNode& node) {
if (node.arguments.size() < N_start + N) {
throw_renderer_error("function needs " + std::to_string(N_start + N) + " variables, but has only found " + std::to_string(node.arguments.size()), node);
}
for (size_t i = N_start; i < N_start + N; i += 1) {
node.arguments[i]->accept(*this);
}
if (json_eval_stack.size() < N) {
throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node);
}
std::array<const json*, N> result;
for (size_t i = 0; i < N; i += 1) {
result[N - i - 1] = json_eval_stack.top();
json_eval_stack.pop();
if (!result[N - i - 1]) {
const auto json_node = not_found_stack.top();
not_found_stack.pop();
if (throw_not_found) {
throw_renderer_error("variable '" + static_cast<std::string>(json_node->name) + "' not found", *json_node);
}
}
}
return result;
}
template<bool throw_not_found=true>
Arguments get_argument_vector(const FunctionNode& node) {
const size_t N = node.arguments.size();
for (auto a: node.arguments) {
a->accept(*this);
}
if (json_eval_stack.size() < N) {
throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node);
}
Arguments result {N};
for (size_t i = 0; i < N; i += 1) {
result[N - i - 1] = json_eval_stack.top();
json_eval_stack.pop();
if (!result[N - i - 1]) {
const auto json_node = not_found_stack.top();
not_found_stack.pop();
if (throw_not_found) {
throw_renderer_error("variable '" + static_cast<std::string>(json_node->name) + "' not found", *json_node);
}
}
}
return result;
}
void visit(const BlockNode& node) {
for (auto& n : node.nodes) {
n->accept(*this);
if (break_rendering) {
break;
}
}
}
void visit(const TextNode& node) {
output_stream->write(current_template->content.c_str() + node.pos, node.length);
}
void visit(const ExpressionNode&) { }
void visit(const LiteralNode& node) {
json_eval_stack.push(&node.value);
}
void visit(const JsonNode& node) {
if (json_additional_data.contains(node.ptr)) {
json_eval_stack.push(&(json_additional_data[node.ptr]));
} else if (json_input->contains(node.ptr)) {
json_eval_stack.push(&(*json_input)[node.ptr]);
} else {
// Try to evaluate as a no-argument callback
const auto function_data = function_storage.find_function(node.name, 0);
if (function_data.operation == FunctionStorage::Operation::Callback) {
Arguments empty_args {};
const auto value = std::make_shared<json>(function_data.callback(empty_args));
json_tmp_stack.push_back(value);
json_eval_stack.push(value.get());
} else {
json_eval_stack.push(nullptr);
not_found_stack.emplace(&node);
}
}
}
void visit(const FunctionNode& node) {
std::shared_ptr<json> result_ptr;
switch (node.operation) {
case Op::Not: {
const auto args = get_arguments<1>(node);
result_ptr = std::make_shared<json>(!truthy(args[0]));
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::And: {
result_ptr = std::make_shared<json>(truthy(get_arguments<1, 0>(node)[0]) && truthy(get_arguments<1, 1>(node)[0]));
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Or: {
result_ptr = std::make_shared<json>(truthy(get_arguments<1, 0>(node)[0]) || truthy(get_arguments<1, 1>(node)[0]));
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::In: {
const auto args = get_arguments<2>(node);
result_ptr = std::make_shared<json>(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end());
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Equal: {
const auto args = get_arguments<2>(node);
result_ptr = std::make_shared<json>(*args[0] == *args[1]);
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::NotEqual: {
const auto args = get_arguments<2>(node);
result_ptr = std::make_shared<json>(*args[0] != *args[1]);
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Greater: {
const auto args = get_arguments<2>(node);
result_ptr = std::make_shared<json>(*args[0] > *args[1]);
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::GreaterEqual: {
const auto args = get_arguments<2>(node);
result_ptr = std::make_shared<json>(*args[0] >= *args[1]);
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Less: {
const auto args = get_arguments<2>(node);
result_ptr = std::make_shared<json>(*args[0] < *args[1]);
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::LessEqual: {
const auto args = get_arguments<2>(node);
result_ptr = std::make_shared<json>(*args[0] <= *args[1]);
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Add: {
const auto args = get_arguments<2>(node);
if (args[0]->is_string() && args[1]->is_string()) {
result_ptr = std::make_shared<json>(args[0]->get_ref<const std::string&>() + args[1]->get_ref<const std::string&>());
json_tmp_stack.push_back(result_ptr);
} else if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
result_ptr = std::make_shared<json>(args[0]->get<int>() + args[1]->get<int>());
json_tmp_stack.push_back(result_ptr);
} else {
result_ptr = std::make_shared<json>(args[0]->get<double>() + args[1]->get<double>());
json_tmp_stack.push_back(result_ptr);
}
json_eval_stack.push(result_ptr.get());
} break;
case Op::Subtract: {
const auto args = get_arguments<2>(node);
if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
result_ptr = std::make_shared<json>(args[0]->get<int>() - args[1]->get<int>());
json_tmp_stack.push_back(result_ptr);
} else {
result_ptr = std::make_shared<json>(args[0]->get<double>() - args[1]->get<double>());
json_tmp_stack.push_back(result_ptr);
}
json_eval_stack.push(result_ptr.get());
} break;
case Op::Multiplication: {
const auto args = get_arguments<2>(node);
if (args[0]->is_number_integer() && args[1]->is_number_integer()) {
result_ptr = std::make_shared<json>(args[0]->get<int>() * args[1]->get<int>());
json_tmp_stack.push_back(result_ptr);
} else {
result_ptr = std::make_shared<json>(args[0]->get<double>() * args[1]->get<double>());
json_tmp_stack.push_back(result_ptr);
}
json_eval_stack.push(result_ptr.get());
} break;
case Op::Division: {
const auto args = get_arguments<2>(node);
if (args[1]->get<double>() == 0) {
throw_renderer_error("division by zero", node);
}
result_ptr = std::make_shared<json>(args[0]->get<double>() / args[1]->get<double>());
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Power: {
const auto args = get_arguments<2>(node);
if (args[0]->is_number_integer() && args[1]->get<int>() >= 0) {
int result = static_cast<int>(std::pow(args[0]->get<int>(), args[1]->get<int>()));
result_ptr = std::make_shared<json>(std::move(result));
json_tmp_stack.push_back(result_ptr);
} else {
double result = std::pow(args[0]->get<double>(), args[1]->get<int>());
result_ptr = std::make_shared<json>(std::move(result));
json_tmp_stack.push_back(result_ptr);
}
json_eval_stack.push(result_ptr.get());
} break;
case Op::Modulo: {
const auto args = get_arguments<2>(node);
result_ptr = std::make_shared<json>(args[0]->get<int>() % args[1]->get<int>());
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::AtId: {
const auto container = get_arguments<1, 0, false>(node)[0];
node.arguments[1]->accept(*this);
if (not_found_stack.empty()) {
throw_renderer_error("could not find element with given name", node);
}
const auto id_node = not_found_stack.top();
not_found_stack.pop();
json_eval_stack.pop();
json_eval_stack.push(&container->at(id_node->name));
} break;
case Op::At: {
const auto args = get_arguments<2>(node);
if (args[0]->is_object()) {
json_eval_stack.push(&args[0]->at(args[1]->get<std::string>()));
} else {
json_eval_stack.push(&args[0]->at(args[1]->get<int>()));
}
} break;
case Op::Default: {
const auto test_arg = get_arguments<1, 0, false>(node)[0];
json_eval_stack.push(test_arg ? test_arg : get_arguments<1, 1>(node)[0]);
} break;
case Op::DivisibleBy: {
const auto args = get_arguments<2>(node);
const int divisor = args[1]->get<int>();
result_ptr = std::make_shared<json>((divisor != 0) && (args[0]->get<int>() % divisor == 0));
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Even: {
result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->get<int>() % 2 == 0);
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Exists: {
auto &&name = get_arguments<1>(node)[0]->get_ref<const std::string &>();
result_ptr = std::make_shared<json>(json_input->contains(json::json_pointer(JsonNode::convert_dot_to_json_ptr(name))));
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::ExistsInObject: {
const auto args = get_arguments<2>(node);
auto &&name = args[1]->get_ref<const std::string &>();
result_ptr = std::make_shared<json>(args[0]->find(name) != args[0]->end());
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::First: {
const auto result = &get_arguments<1>(node)[0]->front();
json_eval_stack.push(result);
} break;
case Op::Float: {
result_ptr = std::make_shared<json>(std::stod(get_arguments<1>(node)[0]->get_ref<const std::string &>()));
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Int: {
result_ptr = std::make_shared<json>(std::stoi(get_arguments<1>(node)[0]->get_ref<const std::string &>()));
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Last: {
const auto result = &get_arguments<1>(node)[0]->back();
json_eval_stack.push(result);
} break;
case Op::Length: {
const auto val = get_arguments<1>(node)[0];
if (val->is_string()) {
result_ptr = std::make_shared<json>(val->get_ref<const std::string &>().length());
} else {
result_ptr = std::make_shared<json>(val->size());
}
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Lower: {
std::string result = get_arguments<1>(node)[0]->get<std::string>();
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
result_ptr = std::make_shared<json>(std::move(result));
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Max: {
const auto args = get_arguments<1>(node);
const auto result = std::max_element(args[0]->begin(), args[0]->end());
json_eval_stack.push(&(*result));
} break;
case Op::Min: {
const auto args = get_arguments<1>(node);
const auto result = std::min_element(args[0]->begin(), args[0]->end());
json_eval_stack.push(&(*result));
} break;
case Op::Odd: {
result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->get<int>() % 2 != 0);
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Range: {
std::vector<int> result(get_arguments<1>(node)[0]->get<int>());
std::iota(result.begin(), result.end(), 0);
result_ptr = std::make_shared<json>(std::move(result));
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Round: {
const auto args = get_arguments<2>(node);
const int precision = args[1]->get<int>();
const double result = std::round(args[0]->get<double>() * std::pow(10.0, precision)) / std::pow(10.0, precision);
if(0==precision){
result_ptr = std::make_shared<json>(int(result));
}else{
result_ptr = std::make_shared<json>(std::move(result));
}
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Sort: {
result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->get<std::vector<json>>());
std::sort(result_ptr->begin(), result_ptr->end());
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Upper: {
std::string result = get_arguments<1>(node)[0]->get<std::string>();
std::transform(result.begin(), result.end(), result.begin(), ::toupper);
result_ptr = std::make_shared<json>(std::move(result));
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::IsBoolean: {
result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_boolean());
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::IsNumber: {
result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_number());
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::IsInteger: {
result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_number_integer());
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::IsFloat: {
result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_number_float());
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::IsObject: {
result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_object());
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::IsArray: {
result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_array());
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::IsString: {
result_ptr = std::make_shared<json>(get_arguments<1>(node)[0]->is_string());
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Callback: {
auto args = get_argument_vector(node);
result_ptr = std::make_shared<json>(node.callback(args));
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Super: {
const auto args = get_argument_vector(node);
const size_t old_level = current_level;
const size_t level_diff = (args.size() == 1) ? args[0]->get<int>() : 1;
const size_t level = current_level + level_diff;
if (block_statement_stack.empty()) {
throw_renderer_error("super() call is not within a block", node);
}
if (level < 1 || level > template_stack.size() - 1) {
throw_renderer_error("level of super() call does not match parent templates (between 1 and " + std::to_string(template_stack.size() - 1) + ")", node);
}
const auto current_block_statement = block_statement_stack.back();
const Template *new_template = template_stack.at(level);
const Template *old_template = current_template;
const auto block_it = new_template->block_storage.find(current_block_statement->name);
if (block_it != new_template->block_storage.end()) {
current_template = new_template;
current_level = level;
block_it->second->block.accept(*this);
current_level = old_level;
current_template = old_template;
} else {
throw_renderer_error("could not find block with name '" + current_block_statement->name + "'", node);
}
result_ptr = std::make_shared<json>(nullptr);
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::Join: {
const auto args = get_arguments<2>(node);
const auto separator = args[1]->get<std::string>();
std::ostringstream os;
std::string sep;
for (const auto& value : *args[0]) {
os << sep;
if (value.is_string()) {
os << value.get<std::string>(); // otherwise the value is surrounded with ""
} else {
os << value;
}
sep = separator;
}
result_ptr = std::make_shared<json>(os.str());
json_tmp_stack.push_back(result_ptr);
json_eval_stack.push(result_ptr.get());
} break;
case Op::ParenLeft:
case Op::ParenRight:
case Op::None:
break;
}
}
void visit(const ExpressionListNode& node) {
print_json(eval_expression_list(node));
}
void visit(const StatementNode&) { }
void visit(const ForStatementNode&) { }
void visit(const ForArrayStatementNode& node) {
const auto result = eval_expression_list(node.condition);
if (!result->is_array()) {
throw_renderer_error("object must be an array", node);
}
if (!current_loop_data->empty()) {
auto tmp = *current_loop_data; // Because of clang-3
(*current_loop_data)["parent"] = std::move(tmp);
}
size_t index = 0;
(*current_loop_data)["is_first"] = true;
(*current_loop_data)["is_last"] = (result->size() <= 1);
for (auto it = result->begin(); it != result->end(); ++it) {
json_additional_data[static_cast<std::string>(node.value)] = *it;
(*current_loop_data)["index"] = index;
(*current_loop_data)["index1"] = index + 1;
if (index == 1) {
(*current_loop_data)["is_first"] = false;
}
if (index == result->size() - 1) {
(*current_loop_data)["is_last"] = true;
}
node.body.accept(*this);
++index;
}
json_additional_data[static_cast<std::string>(node.value)].clear();
if (!(*current_loop_data)["parent"].empty()) {
const auto tmp = (*current_loop_data)["parent"];
*current_loop_data = std::move(tmp);
} else {
current_loop_data = &json_additional_data["loop"];
}
}
void visit(const ForObjectStatementNode& node) {
const auto result = eval_expression_list(node.condition);
if (!result->is_object()) {
throw_renderer_error("object must be an object", node);
}
if (!current_loop_data->empty()) {
(*current_loop_data)["parent"] = std::move(*current_loop_data);
}
size_t index = 0;
(*current_loop_data)["is_first"] = true;
(*current_loop_data)["is_last"] = (result->size() <= 1);
for (auto it = result->begin(); it != result->end(); ++it) {
json_additional_data[static_cast<std::string>(node.key)] = it.key();
json_additional_data[static_cast<std::string>(node.value)] = it.value();
(*current_loop_data)["index"] = index;
(*current_loop_data)["index1"] = index + 1;
if (index == 1) {
(*current_loop_data)["is_first"] = false;
}
if (index == result->size() - 1) {
(*current_loop_data)["is_last"] = true;
}
node.body.accept(*this);
++index;
}
json_additional_data[static_cast<std::string>(node.key)].clear();
json_additional_data[static_cast<std::string>(node.value)].clear();
if (!(*current_loop_data)["parent"].empty()) {
*current_loop_data = std::move((*current_loop_data)["parent"]);
} else {
current_loop_data = &json_additional_data["loop"];
}
}
void visit(const IfStatementNode& node) {
const auto result = eval_expression_list(node.condition);
if (truthy(result.get())) {
node.true_statement.accept(*this);
} else if (node.has_false_statement) {
node.false_statement.accept(*this);
}
}
void visit(const IncludeStatementNode& node) {
auto sub_renderer = Renderer(config, template_storage, function_storage);
const auto included_template_it = template_storage.find(node.file);
if (included_template_it != template_storage.end()) {
sub_renderer.render_to(*output_stream, included_template_it->second, *json_input, &json_additional_data);
} else if (config.throw_at_missing_includes) {
throw_renderer_error("include '" + node.file + "' not found", node);
}
}
void visit(const ExtendsStatementNode& node) {
const auto included_template_it = template_storage.find(node.file);
if (included_template_it != template_storage.end()) {
const Template *parent_template = &included_template_it->second;
render_to(*output_stream, *parent_template, *json_input, &json_additional_data);
break_rendering = true;
} else if (config.throw_at_missing_includes) {
throw_renderer_error("extends '" + node.file + "' not found", node);
}
}
void visit(const BlockStatementNode& node) {
const size_t old_level = current_level;
current_level = 0;
current_template = template_stack.front();
const auto block_it = current_template->block_storage.find(node.name);
if (block_it != current_template->block_storage.end()) {
block_statement_stack.emplace_back(&node);
block_it->second->block.accept(*this);
block_statement_stack.pop_back();
}
current_level = old_level;
current_template = template_stack.back();
}
void visit(const SetStatementNode& node) {
std::string ptr = node.key;
replace_substring(ptr, ".", "/");
ptr = "/" + ptr;
json_additional_data[nlohmann::json::json_pointer(ptr)] = *eval_expression_list(node.expression);
}
public:
Renderer(const RenderConfig& config, const TemplateStorage &template_storage, const FunctionStorage &function_storage)
: config(config), template_storage(template_storage), function_storage(function_storage) { }
void render_to(std::ostream &os, const Template &tmpl, const json &data, json *loop_data = nullptr) {
output_stream = &os;
current_template = &tmpl;
json_input = &data;
if (loop_data) {
json_additional_data = *loop_data;
current_loop_data = &json_additional_data["loop"];
}
template_stack.emplace_back(current_template);
current_template->root.accept(*this);
json_tmp_stack.clear();
}
};
} // namespace inja
#endif // INCLUDE_INJA_RENDERER_HPP_

View File

@ -0,0 +1,74 @@
#ifndef INCLUDE_INJA_STATISTICS_HPP_
#define INCLUDE_INJA_STATISTICS_HPP_
#include "node.hpp"
namespace inja {
/*!
* \brief A class for counting statistics on a Template.
*/
class StatisticsVisitor : public NodeVisitor {
void visit(const BlockNode& node) {
for (auto& n : node.nodes) {
n->accept(*this);
}
}
void visit(const TextNode&) { }
void visit(const ExpressionNode&) { }
void visit(const LiteralNode&) { }
void visit(const JsonNode&) {
variable_counter += 1;
}
void visit(const FunctionNode& node) {
for (auto& n : node.arguments) {
n->accept(*this);
}
}
void visit(const ExpressionListNode& node) {
node.root->accept(*this);
}
void visit(const StatementNode&) { }
void visit(const ForStatementNode&) { }
void visit(const ForArrayStatementNode& node) {
node.condition.accept(*this);
node.body.accept(*this);
}
void visit(const ForObjectStatementNode& node) {
node.condition.accept(*this);
node.body.accept(*this);
}
void visit(const IfStatementNode& node) {
node.condition.accept(*this);
node.true_statement.accept(*this);
node.false_statement.accept(*this);
}
void visit(const IncludeStatementNode&) { }
void visit(const ExtendsStatementNode&) { }
void visit(const BlockStatementNode& node) {
node.block.accept(*this);
}
void visit(const SetStatementNode&) { }
public:
unsigned int variable_counter;
explicit StatisticsVisitor() : variable_counter(0) { }
};
} // namespace inja
#endif // INCLUDE_INJA_STATISTICS_HPP_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
#ifndef INCLUDE_INJA_TEMPLATE_HPP_
#define INCLUDE_INJA_TEMPLATE_HPP_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "node.hpp"
#include "statistics.hpp"
namespace inja {
/*!
* \brief The main inja Template.
*/
struct Template {
BlockNode root;
std::string content;
std::map<std::string, std::shared_ptr<BlockStatementNode>> block_storage;
explicit Template() { }
explicit Template(const std::string& content): content(content) { }
/// Return number of variables (total number, not distinct ones) in the template
int count_variables() {
auto statistic_visitor = StatisticsVisitor();
root.accept(statistic_visitor);
return statistic_visitor.variable_counter;
}
};
using TemplateStorage = std::map<std::string, Template>;
} // namespace inja
#endif // INCLUDE_INJA_TEMPLATE_HPP_

74
tools/jsonproc/token.hpp Normal file
View File

@ -0,0 +1,74 @@
#ifndef INCLUDE_INJA_TOKEN_HPP_
#define INCLUDE_INJA_TOKEN_HPP_
#include <string>
#include "string_view.hpp"
namespace inja {
/*!
* \brief Helper-class for the inja Lexer.
*/
struct Token {
enum class Kind {
Text,
ExpressionOpen, // {{
ExpressionClose, // }}
LineStatementOpen, // ##
LineStatementClose, // \n
StatementOpen, // {%
StatementClose, // %}
CommentOpen, // {#
CommentClose, // #}
Id, // this, this.foo
Number, // 1, 2, -1, 5.2, -5.3
String, // "this"
Plus, // +
Minus, // -
Times, // *
Slash, // /
Percent, // %
Power, // ^
Comma, // ,
Dot, // .
Colon, // :
LeftParen, // (
RightParen, // )
LeftBracket, // [
RightBracket, // ]
LeftBrace, // {
RightBrace, // }
Equal, // ==
NotEqual, // !=
GreaterThan, // >
GreaterEqual, // >=
LessThan, // <
LessEqual, // <=
Unknown,
Eof,
};
Kind kind {Kind::Unknown};
nonstd::string_view text;
explicit constexpr Token() = default;
explicit constexpr Token(Kind kind, nonstd::string_view text) : kind(kind), text(text) {}
std::string describe() const {
switch (kind) {
case Kind::Text:
return "<text>";
case Kind::LineStatementClose:
return "<eol>";
case Kind::Eof:
return "<eof>";
default:
return static_cast<std::string>(text);
}
}
};
} // namespace inja
#endif // INCLUDE_INJA_TOKEN_HPP_

83
tools/jsonproc/utils.hpp Normal file
View File

@ -0,0 +1,83 @@
#ifndef INCLUDE_INJA_UTILS_HPP_
#define INCLUDE_INJA_UTILS_HPP_
#include <algorithm>
#include <fstream>
#include <string>
#include <utility>
#include "exceptions.hpp"
#include "string_view.hpp"
namespace inja {
inline void open_file_or_throw(const std::string &path, std::ifstream &file) {
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
#ifndef INJA_NOEXCEPTION
try {
file.open(path);
} catch (const std::ios_base::failure & /*e*/) {
INJA_THROW(FileError("failed accessing file at '" + path + "'"));
}
#else
file.open(path);
#endif
}
namespace string_view {
inline nonstd::string_view slice(nonstd::string_view view, size_t start, size_t end) {
start = std::min(start, view.size());
end = std::min(std::max(start, end), view.size());
return view.substr(start, end - start);
}
inline std::pair<nonstd::string_view, nonstd::string_view> split(nonstd::string_view view, char Separator) {
size_t idx = view.find(Separator);
if (idx == nonstd::string_view::npos) {
return std::make_pair(view, nonstd::string_view());
}
return std::make_pair(slice(view, 0, idx), slice(view, idx + 1, nonstd::string_view::npos));
}
inline bool starts_with(nonstd::string_view view, nonstd::string_view prefix) {
return (view.size() >= prefix.size() && view.compare(0, prefix.size(), prefix) == 0);
}
} // namespace string_view
inline SourceLocation get_source_location(nonstd::string_view content, size_t pos) {
// Get line and offset position (starts at 1:1)
auto sliced = string_view::slice(content, 0, pos);
std::size_t last_newline = sliced.rfind("\n");
if (last_newline == nonstd::string_view::npos) {
return {1, sliced.length() + 1};
}
// Count newlines
size_t count_lines = 0;
size_t search_start = 0;
while (search_start <= sliced.size()) {
search_start = sliced.find("\n", search_start) + 1;
if (search_start == 0) {
break;
}
count_lines += 1;
}
return {count_lines + 1, sliced.length() - last_newline};
}
inline void replace_substring(std::string& s, const std::string& f,
const std::string& t)
{
if (f.empty()) return;
for (auto pos = s.find(f); // find first occurrence of f
pos != std::string::npos; // make sure f was found
s.replace(pos, f.size(), t), // replace with t, and
pos = s.find(f, pos + t.size())) // find next occurrence of f
{}
}
} // namespace inja
#endif // INCLUDE_INJA_UTILS_HPP_

177
tools/msgenc/Gmm.cpp Normal file
View File

@ -0,0 +1,177 @@
#include "Gmm.h"
#include "pugixml.hpp"
#include <regex>
static const char WINCTXNAME[] = "window_context_name";
static const char LANGUAGE[] = "English";
static const char JAPANESE[] = "日本語";
// Reads header constants from the supplied file.
// Expects them to be of the format `#define +{name} +{integer value}`
// and that the integer value is in sequential order starting from 0.
void GMM::ReadGmmHeader(const string &_filename) {
ifstream hstrm(_filename, ios::ate | ios::binary);
if (!hstrm.good()) {
return;
}
int fsize = hstrm.tellg();
auto buf = new char[fsize + 1];
hstrm.seekg(0);
hstrm.read(buf, fsize);
buf[fsize] = 0;
string hstrng(buf);
regex pattern(R"(#define\s+(\w+)\s+([0-9]+))");
smatch results;
id_strings.clear();
while (regex_search(hstrng, results, pattern)) {
id_strings.emplace_back(results[1]);
hstrng = results.suffix().str();
}
delete[] buf;
}
// Reads header constants to the supplied file.
// Prints them in the format `#define {name} {integer value}`
// such that the integer value is in sequential order starting from 0.
void GMM::WriteGmmHeader(const string &_filename) {
ofstream hstrm(_filename);
string guard(_filename);
for (auto & c : guard) {
switch (c) {
case '/':
case '.':
c = '_';
break;
default:
c = toupper(c);
break;
}
}
hstrm << "/***************************************************" << endl;
hstrm << " * WARNING: This file was autogenerated by msgenc. *" << endl;
hstrm << " * DO NOT MODIFY *" << endl;
hstrm << " ***************************************************/" << endl;
hstrm << endl;
hstrm << "#ifndef MSGENC_" << guard << endl;
hstrm << "#define MSGENC_" << guard << endl;
hstrm << endl;
for (size_t i = 0; i < id_strings.size(); i++) {
auto message_lines = SplitMessage(messages[i]);
for (const auto & message_line : message_lines) {
hstrm << "// " << message_line << endl;
}
hstrm << "#define " << id_strings[i] << " " << i << endl;
}
hstrm << endl;
hstrm << "#endif //MSGENC_" << guard << endl;
}
// Reads messages from GMM into memory to be converted
void GMM::FromFile(MessagesConverter &converter) {
pugi::xml_parse_result result = doc.load(stream);
if (!result) {
throw runtime_error(result.description());
}
const auto &node = doc.find_child([](const auto &n) {
return strcmp(n.name(), "body") == 0;
});
int i = 0;
string rowname_pref = filename.substr(filename.find_last_of('/') + 1).substr(0, filename.find_first_of('.'));
for (const auto &subnode : node.children()) {
if (strcmp(subnode.name(), "row") == 0) {
const auto &language = subnode.find_child([](const auto &n) {
return strcmp(n.name(), "language") == 0 && strcmp(n.attribute("name").value(), LANGUAGE) == 0;
});
const auto &windowcontext = subnode.find_child([](const auto &n) {
return strcmp(n.name(), "attribute") == 0 && strcmp(n.attribute("name").value(), WINCTXNAME) == 0;
});
string is_garbage(windowcontext.child_value());
string message(language.child_value());
if (is_garbage == "garbage") {
const auto &jap = subnode.find_child([](const auto &n) {
return strcmp(n.name(), "language") == 0 && strcmp(n.attribute("name").value(), JAPANESE) == 0;
});
message.assign(jap.child_value());
fill(message.begin(), message.end(), ' ');
}
converter.GetDecodedMessages().emplace_back(message);
string row_id(subnode.attribute("id").value());
if (row_id.empty()) {
row_id = rowname_pref + '_' + row_no_buf;
}
id_strings.emplace_back(row_id);
messages.emplace_back(message);
i++;
IncRowNoBuf();
}
}
if (!converter.GetHeaderFilename().empty()) {
WriteGmmHeader(converter.GetHeaderFilename());
}
}
// Writes decoded messages to a new GMM file
void GMM::ToFile(MessagesConverter &converter) {
if (!converter.GetHeaderFilename().empty()) {
ReadGmmHeader(converter.GetHeaderFilename());
}
auto it = id_strings.cbegin();
auto body = doc.append_child("body");
body.append_attribute("language").set_value(LANGUAGE);
if (body.empty()) {
throw runtime_error("failed to create gmm body node");
}
string rowname_pref = filename.substr(filename.find_last_of('/') + 1);
rowname_pref = rowname_pref.substr(0, rowname_pref.find_first_of('.'));
int i = 0;
for (const auto &message : converter.GetDecodedMessages()) {
string rowname;
if (it != id_strings.cend()) {
rowname = *it++;
} else {
rowname = rowname_pref + '_' + row_no_buf;
}
auto row = body.append_child("row");
if (row.empty()) {
throw runtime_error("failed to create gmm row node");
}
row.append_attribute("id").set_value(rowname.c_str());
row.append_attribute("index").set_value(i++);
auto windowcontextname = row.append_child("attribute");
windowcontextname.append_attribute("name").set_value(WINCTXNAME);
auto language = row.append_child("language");
if (language.empty()) {
throw runtime_error("failed to create gmm language node");
}
language.append_attribute("name").set_value(LANGUAGE);
auto windowattr = windowcontextname.append_child(pugi::xml_node_type::node_pcdata);
if (message.find_first_not_of(' ') == string::npos) {
string buf;
buf.resize(message.size());
fill(buf.begin(), buf.end(), 'X');
windowattr.set_value("garbage");
auto jap = row.append_child("language");
jap.append_attribute("name").set_value(JAPANESE);
jap.append_child(pugi::xml_node_type::node_pcdata).set_value(buf.c_str());
language.append_child(pugi::xml_node_type::node_pcdata).set_value("");
} else {
windowattr.set_value("used");
language.append_child(pugi::xml_node_type::node_pcdata).set_value(message.c_str());
}
IncRowNoBuf();
}
doc.save(stream);
}
// The message headers are included in script files, which can cause the
// assembler to break if the lines are too long.
vector<string> GMM::SplitMessage(const string &message) {
vector<string> v;
auto limit = 300;
for (size_t i = 0; i < message.size(); i += limit) {
v.push_back(message.substr(i, limit));
}
return v;
}

57
tools/msgenc/Gmm.h Normal file
View File

@ -0,0 +1,57 @@
#ifndef GUARD_GMM_H
#define GUARD_GMM_H
// The GMM format is a slimmed-down variant of GameFreak's GMM file.
// It is essentially an XML file by another name.
// Within the body, each row encodes the text of a message as well
// as additional details about how the message is printed.
// Most of these details are ignored.
// Some strings in the EN-US release of Pokemon titles are blank
// (composed entirely of spaces). The number of spaces varies by the
// length of the corresponding Japanese message. Such strings are
// labeled with the window context "garbage".
#include <string>
#include <vector>
#include <fstream>
#include <cstring>
#include "pugixml.hpp"
#include "MessagesConverter.h"
class GMM {
static const int _row_no_buf_ndigit = 5;
char row_no_buf[_row_no_buf_ndigit + 1] = {0};
string filename;
fstream stream;
pugi::xml_document doc;
vector<string> id_strings;
vector<string> messages;
void ReadGmmHeader(const string &_filename);
void WriteGmmHeader(const string &_filename);
void IncRowNoBuf() {
for (int i = _row_no_buf_ndigit - 1; i >= 0; i--) {
row_no_buf[i]++;
if (row_no_buf[i] > '9') {
if (i == 0) {
throw runtime_error("message count overflow");
}
row_no_buf[i] = '0';
} else {
break;
}
}
}
public:
GMM(string &_filename, ios::openmode _openmode) : filename(_filename), stream(_filename, _openmode) {
memset(row_no_buf, '0', _row_no_buf_ndigit);
}
void FromFile(MessagesConverter &converter);
void ToFile(MessagesConverter &converter);
private:
vector<string> SplitMessage(const string &message);
};
#endif //GUARD_GMM_H

View File

@ -0,0 +1,24 @@
MIT License
Copyright (c) 2006-2020 Arseny Kapoulkine
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.

View File

@ -1,5 +1,6 @@
CXXFLAGS := -std=c++17 -O2 -Wall -Wno-switch
CFLAGS := -O2 -Wall -Wno-switch
LDFLAGS :=
ifeq ($(DEBUG),)
CXXFLAGS += -DNDEBUG
@ -10,9 +11,12 @@ DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d
SRCS := \
msgenc.cpp \
Options.cpp \
MessagesConverter.cpp \
MessagesDecoder.cpp \
MessagesEncoder.cpp
MessagesEncoder.cpp \
Gmm.cpp \
pugixml.cpp
OBJS := $(SRCS:%.cpp=%.o)

View File

@ -2,6 +2,7 @@
#define GUARD_MESSAGESCONVERTER_H
#include "util.h"
#include "Options.h"
#include <string>
#include <fstream>
#include <map>
@ -17,12 +18,6 @@ static inline uint16_t enc_short(uint16_t value, uint16_t & seed) {
return value;
}
enum ConvertMode : uint8_t {
CONV_ENCODE = 0,
CONV_DECODE,
CONV_INVALID = 0xFF,
};
struct MsgArcHeader
{
uint16_t count;
@ -62,6 +57,7 @@ protected:
string textfilename;
string charmapfilename;
string binfilename;
string headerfilename;
MsgArcHeader header = {};
vector<MsgAlloc> alloc_table;
@ -77,13 +73,20 @@ protected:
static void WriteTextFile(string& filename, string const & contents);
public:
MessagesConverter(ConvertMode _mode, string &_textfilename, int _key, string &_charmapfilename, string &_binfilename) :
mode(_mode),
textfilename(_textfilename),
charmapfilename(_charmapfilename),
binfilename(_binfilename)
typedef int txtfmt;
static const txtfmt PlainText = 0;
static const txtfmt GamefreakGMM = 1;
protected:
txtfmt text_format = PlainText;
public:
MessagesConverter(Options &options) :
mode(options.mode),
charmapfilename(options.charmap),
headerfilename(options.gmm_header),
text_format(options.textFormat)
{
header.key = (_key == 0) ? CalcCRC() : static_cast<uint16_t>(_key);
header.key = (options.key == 0) ? CalcCRC() : static_cast<uint16_t>(options.key);
}
void ReadCharmap();
virtual void ReadInput() = 0;
@ -98,6 +101,17 @@ public:
}
void WriteBinaryDecoded(string &filename);
vector<string> &GetDecodedMessages() {
return vec_decoded;
}
vector<u16string> &GetEncodedMessages() {
return vec_encoded;
}
string &GetHeaderFilename() {
return headerfilename;
}
};
#endif //GUARD_MESSAGESCONVERTER_H

View File

@ -1,5 +1,6 @@
#include <algorithm>
#include "MessagesDecoder.h"
#include "Gmm.h"
void MessagesDecoder::CmdmapRegisterCommand(string &command, uint16_t value)
{
@ -163,6 +164,10 @@ void MessagesDecoder::WriteMessagesToText(string& filename) {
WriteTextFile(filename, ss.str());
}
void MessagesDecoder::WriteMessagesToGMM(string &filename) {
GMM(filename, std::ios::out).ToFile(*this);
}
// Public virtual functions
void MessagesDecoder::ReadInput()
@ -181,5 +186,12 @@ void MessagesDecoder::Convert()
void MessagesDecoder::WriteOutput()
{
WriteMessagesToText(textfilename);
switch (text_format) {
case PlainText:
WriteMessagesToText(textfilename);
break;
case GamefreakGMM:
WriteMessagesToGMM(textfilename);
break;
}
}

View File

@ -18,6 +18,7 @@ class MessagesDecoder : public MessagesConverter
void ReadMessagesFromBin(string& filename);
void WriteMessagesToText(string& filename);
void WriteMessagesToGMM(string& filename);
template <typename T> void WriteBinaryFile(string& filename, T& data);
static u16string DecodeTrainerNameMessage(u16string const &message);
string DecodeMessage(u16string& message, int& i);
@ -25,7 +26,10 @@ class MessagesDecoder : public MessagesConverter
void CmdmapRegisterCommand(string& command, uint16_t value) override;
public:
MessagesDecoder(string &_textfilename, int _key, string &_charmapfilename, string &_binfilename) : MessagesConverter(CONV_DECODE, _textfilename, _key, _charmapfilename, _binfilename) {}
MessagesDecoder(Options &options) : MessagesConverter(options) {
textfilename = options.posargs[1];
binfilename = options.posargs[0];
}
void ReadInput() override;
void Convert() override;
void WriteOutput() override;

View File

@ -1,4 +1,5 @@
#include "MessagesEncoder.h"
#include "Gmm.h"
void MessagesEncoder::CmdmapRegisterCommand(string &command, uint16_t value)
{
@ -28,6 +29,12 @@ void MessagesEncoder::ReadMessagesFromText(string& fname) {
debug_printf("%d lines\n", header.count);
}
void MessagesEncoder::ReadMessagesFromGMM(string& filename) {
GMM(filename, std::ios::in).FromFile(*this);
header.count = vec_decoded.size();
debug_printf("%d lines\n", header.count);
}
u16string MessagesEncoder::EncodeMessage(const string & message, int & i) {
u16string encoded;
bool is_trname = false;
@ -143,7 +150,14 @@ void MessagesEncoder::WriteMessagesToBin(string& filename) {
void MessagesEncoder::ReadInput()
{
ReadMessagesFromText(textfilename);
switch (text_format) {
case PlainText:
ReadMessagesFromText(textfilename);
break;
case GamefreakGMM:
ReadMessagesFromGMM(textfilename);
break;
}
}
void MessagesEncoder::Convert() {

View File

@ -10,12 +10,16 @@ class MessagesEncoder : public MessagesConverter
map <string, uint16_t> charmap;
void ReadMessagesFromText(string& filename);
void ReadMessagesFromGMM(string& filename);
void WriteMessagesToBin(string& filename);
u16string EncodeMessage(const string& message, int & i);
void CharmapRegisterCharacter(string& code, uint16_t value) override;
void CmdmapRegisterCommand(string& command, uint16_t value) override;
public:
MessagesEncoder(string &_textfilename, int _key, string &_charmapfilename, string &_binfilename) : MessagesConverter(CONV_ENCODE, _textfilename, _key, _charmapfilename, _binfilename) {}
MessagesEncoder(Options &options) : MessagesConverter(options) {
textfilename = options.posargs[0];
binfilename = options.posargs[1];
}
void ReadInput() override;
void Convert() override;
void WriteOutput() override;

45
tools/msgenc/Options.cpp Normal file
View File

@ -0,0 +1,45 @@
#include "Options.h"
Options::Options(int argc, char **argv) {
for (int i = 1; i < argc; i++) {
string arg(argv[i]);
if (arg == "-d") {
mode = CONV_DECODE;
} else if (arg == "-e") {
mode = CONV_ENCODE;
} else if (arg == "-h") {
printUsage = true;
return;
} else if (arg == "-H") {
gmm_header = argv[++i];
} else if (arg == "-v") {
printVersion = true;
return;
} else if (arg == "-k") {
key = stoi(argv[++i], nullptr, 0);
// If the key is 0, ensure that it is not overridden by the CRC.
key &= 0xFFFF;
key |= 0x10000;
} else if (arg == "-c") {
charmap = argv[++i];
} else if (arg == "-D") {
dumpBinary = argv[++i];
} else if (arg == "--gmm") {
textFormat = GamefreakGMM;
} else if (arg[0] != '-') {
posargs.push_back(arg);
} else {
failReason = "unrecognized option: " + arg;
break;
}
}
if (posargs.size() < 2) {
failReason = "missing required positional argument: " + (string[]){"INFILE", "OUTFILE"}[posargs.size()];
}
if (mode == CONV_INVALID) {
failReason = "missing mode flag: -d or -e is required";
}
if (charmap.empty()) {
failReason = "missing charmap file: -c CHARMAP is required";
}
}

34
tools/msgenc/Options.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef GUARD_OPTIONS_H
#define GUARD_OPTIONS_H
#include <cstdint>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
enum ConvertMode : uint8_t {
CONV_ENCODE = 0,
CONV_DECODE,
CONV_INVALID = 0xFF,
};
struct Options {
ConvertMode mode = CONV_INVALID;
int key = 0;
vector<string> posargs;
string failReason;
string charmap;
bool printUsage = false;
bool printVersion = false;
string dumpBinary;
string gmm_header = "";
typedef int txtfmt;
static const txtfmt PlainText = 0;
static const txtfmt GamefreakGMM = 1;
txtfmt textFormat = PlainText;
Options(int argc, char ** argv);
};
#endif //GUARD_OPTIONS_H

View File

@ -8,77 +8,29 @@
#include <iostream>
#include "MessagesDecoder.h"
#include "MessagesEncoder.h"
#include "Options.h"
static const char* progname = "msgenc";
static const char* version = "2021.08.27";
static const char* version = "2021.12.21";
static inline void usage() {
cout << progname << " v" << version << endl;
cout << "Usage: " << progname << " [-h] [-v] -d|-e [-k KEY] -c CHARMAP INFILE OUTFILE" << endl;
cout << "Usage: " << progname << " [-h] [-v] -d|-e [OPTIONS] -c CHARMAP INFILE OUTFILE" << endl;
cout << endl;
cout << "INFILE Required: Path to the input file to convert (-e: plaintext; -d: binary)." << endl;
cout << "OUTFILE Required: Path to the output file (-e: binary; -d: plaintext)." << endl;
cout << "-c CHARMAP Required: Path to a text file with a character mapping, for example pokeheartgold/charmap.txt." << endl;
cout << "-d Decode from binary to text, also print the key" << endl;
cout << "-e Encode from text to binary using the provided key" << endl;
cout << "--gmm Text file is GMM (Gamefreak XML format)" << endl;
cout << "-H HEADER When operating in GMM mode, specify this header file to read/write C constant values" << endl;
cout << "-k KEY The 16-bit encryption key for this message bank. Default: computes it from the binary file name" << endl;
cout << "-v Print the program version and exit." << endl;
cout << "-h Print this message and exit." << endl;
cout << "-D DUMPNAME Dump the intermediate binary (after decryption or before encryption)." << endl;
}
struct Options {
ConvertMode mode = CONV_INVALID;
int key = 0;
vector<string> posargs;
string failReason;
string charmap;
bool printUsage = false;
bool printVersion = false;
string dumpBinary;
Options(int argc, char ** argv) {
for (int i = 1; i < argc; i++) {
string arg(argv[i]);
if (arg == "-d") {
mode = CONV_DECODE;
} else if (arg == "-e") {
mode = CONV_ENCODE;
} else if (arg == "-h") {
printUsage = true;
return;
} else if (arg == "-v") {
printVersion = true;
return;
} else if (arg == "-k") {
key = stoi(argv[++i], nullptr, 0);
// If the key is 0, ensure that it is not overridden by the CRC.
key &= 0xFFFF;
key |= 0x10000;
} else if (arg == "-c") {
charmap = argv[++i];
} else if (arg == "-D") {
dumpBinary = argv[++i];
} else if (arg[0] != '-') {
posargs.push_back(arg);
} else {
failReason = "unrecognized option: " + arg;
break;
}
}
if (posargs.size() < 2) {
failReason = "missing required positional argument: " + (string[]){"INFILE", "OUTFILE"}[posargs.size()];
}
if (mode == CONV_INVALID) {
failReason = "missing mode flag: -d or -e is required";
}
if (charmap.empty()) {
failReason = "missing charmap file: -c CHARMAP is required";
}
}
};
int main(int argc, char ** argv) {
int do_main(MessagesConverter* &converter, int argc, char ** argv) {
try {
Options options(argc, argv);
if (options.printUsage || !options.failReason.empty()) {
@ -92,25 +44,21 @@ int main(int argc, char ** argv) {
return 0;
}
MessagesConverter *converter;
if (options.mode == CONV_DECODE)
{
converter = new MessagesDecoder(options.posargs[1], options.key, options.charmap, options.posargs[0]);
}
else
{
converter = new MessagesEncoder(options.posargs[0], options.key, options.charmap, options.posargs[1]);
if (options.mode == CONV_DECODE) {
converter = new MessagesDecoder(options);
} else {
converter = new MessagesEncoder(options);
}
converter->ReadInput();
converter->ReadCharmap();
converter->Convert();
if (!options.dumpBinary.empty())
if (!options.dumpBinary.empty()) {
converter->WriteBinaryDecoded(options.dumpBinary);
}
converter->WriteOutput();
if (options.mode == CONV_DECODE) {
cout << "Key: " << hex << converter->GetKey() << endl;
}
delete converter;
} catch (invalid_argument& ia) {
cerr << "Invalid Argument: " << ia.what() << endl;
return 1;
@ -123,3 +71,10 @@ int main(int argc, char ** argv) {
}
return 0;
}
int main(int argc, char ** argv) {
MessagesConverter *converter;
int result = do_main(converter, argc, argv);
delete converter;
return result;
}

View File

@ -0,0 +1,77 @@
/**
* pugixml parser - version 1.11
* --------------------------------------------------------
* Copyright (C) 2006-2020, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
* Report bugs and download new versions at https://pugixml.org/
*
* This library is distributed under the MIT License. See notice at the end
* of this file.
*
* This work is based on the pugxml parser, which is:
* Copyright (C) 2003, by Kristen Wegner (kristen@tima.net)
*/
#ifndef HEADER_PUGICONFIG_HPP
#define HEADER_PUGICONFIG_HPP
// Uncomment this to enable wchar_t mode
// #define PUGIXML_WCHAR_MODE
// Uncomment this to enable compact mode
// #define PUGIXML_COMPACT
// Uncomment this to disable XPath
// #define PUGIXML_NO_XPATH
// Uncomment this to disable STL
// #define PUGIXML_NO_STL
// Uncomment this to disable exceptions
// #define PUGIXML_NO_EXCEPTIONS
// Set this to control attributes for public classes/functions, i.e.:
// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL
// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL
// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall
// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead
// Tune these constants to adjust memory-related behavior
// #define PUGIXML_MEMORY_PAGE_SIZE 32768
// #define PUGIXML_MEMORY_OUTPUT_STACK 10240
// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096
// Tune this constant to adjust max nesting for XPath queries
// #define PUGIXML_XPATH_DEPTH_LIMIT 1024
// Uncomment this to switch to header-only version
// #define PUGIXML_HEADER_ONLY
// Uncomment this to enable long long support
// #define PUGIXML_HAS_LONG_LONG
#endif
/**
* Copyright (c) 2006-2020 Arseny Kapoulkine
*
* 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.
*/

13020
tools/msgenc/pugixml.cpp Normal file

File diff suppressed because it is too large Load Diff

1499
tools/msgenc/pugixml.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,15 @@
#ifndef GUARD_UTIL_H
#define GUARD_UTIL_H
#include <cstdio>
#include <cstdarg>
#ifdef __cplusplus
extern "C" {
#endif
static inline __attribute__((format(printf, 1, 2))) void debug_printf(const char * fmt, ...) {
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
static inline __attribute__((format(printf, 1, 2))) void debug_printf(const char *fmt, ...) {
#ifndef NDEBUG
fputs("DEBUG: ", stderr);
va_list va_args;
@ -14,4 +19,17 @@ static inline __attribute__((format(printf, 1, 2))) void debug_printf(const char
#endif //NDEBUG
}
static inline __attribute__((format(printf, 1, 2))) void error_printf(const char *fmt, ...) {
fputs("ERROR: ", stderr);
va_list va_args;
va_start(va_args, fmt);
vfprintf(stderr, fmt, va_args);
va_end(va_args);
exit(1);
}
#ifdef __cplusplus
}
#endif
#endif //GUARD_UTIL_H