mirror of
https://github.com/pret/pokediamond.git
synced 2026-03-21 17:54:29 -05:00
start of XML based msgdata
This commit is contained in:
parent
875c5a5d89
commit
64b73d6b02
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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/&/&/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)
|
||||
|
|
|
|||
3
files/msgdata/msg/.gitignore
vendored
3
files/msgdata/msg/.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
|||
*.bin
|
||||
narc_0559.txt
|
||||
narc_0559.gmm
|
||||
*.h
|
||||
|
|
|
|||
|
|
@ -2,3 +2,5 @@
|
|||
*.pal
|
||||
*.txt
|
||||
*.key
|
||||
*.h
|
||||
*.gmm
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -8,7 +8,6 @@
|
|||
u32 __size = sizeof(struct TrainerData);
|
||||
|
||||
const struct TrainerData __data[] = {
|
||||
{},
|
||||
## for trainer in trdata
|
||||
{
|
||||
// Trainer #{{trainer.index}}, {{ trainer.class }} {{ trainer.name }}
|
||||
|
|
|
|||
9
files/poketool/trainer/trname.json.txt
Normal file
9
files/poketool/trainer/trname.json.txt
Normal 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>
|
||||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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
79
tools/jsonproc/config.hpp
Normal 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_
|
||||
228
tools/jsonproc/environment.hpp
Normal file
228
tools/jsonproc/environment.hpp
Normal 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_
|
||||
48
tools/jsonproc/exceptions.hpp
Normal file
48
tools/jsonproc/exceptions.hpp
Normal 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_
|
||||
142
tools/jsonproc/function_storage.hpp
Normal file
142
tools/jsonproc/function_storage.hpp
Normal 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
|
|
@ -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
434
tools/jsonproc/lexer.hpp
Normal 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_
|
||||
21
tools/jsonproc/nlohmann/LICENSE.MIT
Normal file
21
tools/jsonproc/nlohmann/LICENSE.MIT
Normal 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
374
tools/jsonproc/node.hpp
Normal 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
646
tools/jsonproc/parser.hpp
Normal 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
717
tools/jsonproc/renderer.hpp
Normal 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_
|
||||
74
tools/jsonproc/statistics.hpp
Normal file
74
tools/jsonproc/statistics.hpp
Normal 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_
|
||||
1416
tools/jsonproc/string_view.hpp
Normal file
1416
tools/jsonproc/string_view.hpp
Normal file
File diff suppressed because it is too large
Load Diff
38
tools/jsonproc/template.hpp
Normal file
38
tools/jsonproc/template.hpp
Normal 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
74
tools/jsonproc/token.hpp
Normal 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
83
tools/jsonproc/utils.hpp
Normal 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
177
tools/msgenc/Gmm.cpp
Normal 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
57
tools/msgenc/Gmm.h
Normal 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
|
||||
24
tools/msgenc/LICENSE.pugixml.md
Normal file
24
tools/msgenc/LICENSE.pugixml.md
Normal 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.
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
45
tools/msgenc/Options.cpp
Normal 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
34
tools/msgenc/Options.h
Normal 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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
77
tools/msgenc/pugiconfig.hpp
Normal file
77
tools/msgenc/pugiconfig.hpp
Normal 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
13020
tools/msgenc/pugixml.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1499
tools/msgenc/pugixml.hpp
Normal file
1499
tools/msgenc/pugixml.hpp
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user