From 45dc182d095c179ec1c47bd2380047c5da4399c9 Mon Sep 17 00:00:00 2001 From: Sanny Sanoff Date: Sat, 7 Apr 2018 17:49:51 +0300 Subject: [PATCH 01/96] patch to allow unlimited time. on macos, environment setting / launching is inconvenient --- agario/local_runner/mainwindow.h | 17 +++++++++++++++++ agario/local_runner/mainwindow.ui | 7 +++++++ 2 files changed, 24 insertions(+) diff --git a/agario/local_runner/mainwindow.h b/agario/local_runner/mainwindow.h index 3f8eb96..e1dc705 100755 --- a/agario/local_runner/mainwindow.h +++ b/agario/local_runner/mainwindow.h @@ -67,6 +67,7 @@ class MainWindow : public QMainWindow connect(ui->cbx_forces, SIGNAL(stateChanged(int)), this, SLOT(update())); connect(ui->cbx_speed, SIGNAL(stateChanged(int)), this, SLOT(update())); connect(ui->cbx_fog, SIGNAL(stateChanged(int)), this, SLOT(update())); + connect(ui->cbx_notimeout, SIGNAL(stateChanged(int)), this, SLOT(update_notimeout())); connect(ui->btn_strategies_settings, &QPushButton::clicked, sm, &QDialog::show); @@ -169,6 +170,22 @@ public slots: this->update(); } + void update_notimeout() { + static int originalTimeout; + static int originalSumTimeout; + auto &c = Constants::instance(); + if (ui->cbx_notimeout->isChecked()) { + originalTimeout = c.RESP_TIMEOUT; + originalSumTimeout = c.SUM_RESP_TIMEOUT; + c.RESP_TIMEOUT = 999999; + c.SUM_RESP_TIMEOUT = 999999; + } else { + // assuming static values already filled by previous call + c.RESP_TIMEOUT = originalTimeout; + c.SUM_RESP_TIMEOUT = originalSumTimeout; + } + } + void pause_game() { is_paused = !is_paused; if (is_paused) ui->btn_start_pause->setText("Продолжить"); diff --git a/agario/local_runner/mainwindow.ui b/agario/local_runner/mainwindow.ui index 8201714..8491404 100755 --- a/agario/local_runner/mainwindow.ui +++ b/agario/local_runner/mainwindow.ui @@ -199,6 +199,13 @@ + + + + Время неограничено + + + From 2a082d9235e2a7844c05b6bfbe15cb6f79f32318 Mon Sep 17 00:00:00 2001 From: Alexander Kiselev Date: Sat, 1 Sep 2018 01:10:45 +0300 Subject: [PATCH 02/96] Added QuckStartGuy bot for C++ --- madcars/examples/C++11/main.cpp | 102 ++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 madcars/examples/C++11/main.cpp diff --git a/madcars/examples/C++11/main.cpp b/madcars/examples/C++11/main.cpp new file mode 100644 index 0000000..7f8c2ea --- /dev/null +++ b/madcars/examples/C++11/main.cpp @@ -0,0 +1,102 @@ +#include + +#include "../nlohmann/json.hpp" + +using namespace std; + +#define PI 3.14159265358979323846264338327950288 + +int main() { + string input_string, input_type; + int tick(0); + int round(-1); + while (true) { + + getline(cin, input_string); + auto state = nlohmann::json::parse(input_string); + // also possible: + // json state; + // cin >> state; + + input_type = state["type"].get(); + auto params = state["params"]; + + if (input_type == "new_match") { + int my_lives = params["my_lives"].get(); + int enemy_lives = params["enemy_lives"].get(); + + // Example of proto_map parsing. + auto map = params["proto_map"]; + int map_id = map["external_id"].get(); + auto segments = map["segments"]; + for(auto segment:segments){ + auto fp = segment[0]; + auto sp = segment[1]; + double height = segment[2].get(); + } + + auto proto_car = params["proto_car"]; + // etc... + + round++; + tick = 0; + + //cerr << "Round " << round + 1 << " started!" << endl; + + } else if (input_type == "tick") { + //cerr << "Round " << round + 1; + //cerr << " tick " << tick << endl; + + nlohmann::json command; + if (tick < 20) { + // wait a little bit to fall on floor + command["command"] = "stop"; + //cerr << "Waiting..." << endl; + } else { + auto my_car = params["my_car"]; + auto enemy_car = params["enemy_car"]; + + auto my_pos = my_car[0]; + auto enemy_pos = enemy_car[0]; + + // check my and enemy position and go to the enemy + if(my_pos[0].get() > enemy_pos[0].get()) { + command["command"] = "left"; + } else { + command["command"] = "right"; + } + + // roll over in air prevention (this feature can lead to death) + double my_angle = my_car[1].get(); + + // normalize angle + while (my_angle > PI) { + my_angle -= 2.0 * PI; + } + while (my_angle < -PI) { + my_angle += 2.0 * PI; + } + + if (my_angle > PI / 4.0) { + //cerr << "Uhh!" << endl; + command["command"] = "left"; + } else if (my_angle < -PI / 4.0) { + //cerr << "Ahh!" << endl; + command["command"] = "right"; + } else { + //cerr << "Attack!" << endl; + } + } + + //cerr << command.dump() << endl; + cout << command.dump() << endl; + + tick++; + } else { + //cerr << "end_game " << endl; + break; + } + } + + return 0; +} \ No newline at end of file From a4e2b559ff0fd07cc7054febdebe4ae381668467 Mon Sep 17 00:00:00 2001 From: Tongo Hiti Date: Sat, 1 Sep 2018 22:11:08 +0300 Subject: [PATCH 03/96] Square wheels flag support --- madcars/examples/rust/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/madcars/examples/rust/main.rs b/madcars/examples/rust/main.rs index 80f0121..d19db03 100755 --- a/madcars/examples/rust/main.rs +++ b/madcars/examples/rust/main.rs @@ -168,6 +168,8 @@ mod model { pub front_wheel_damp_length: f64, pub front_wheel_damp_stiffness: f64, pub front_wheel_damp_damping: f64, + + pub squared_wheels: Option, } /// Car state From f6dd9268b4c7ded62f7eb3a6020629f34b0fb4f4 Mon Sep 17 00:00:00 2001 From: Tongo Hiti Date: Sat, 1 Sep 2018 22:16:17 +0300 Subject: [PATCH 04/96] Fix: don't answer begin_match message --- madcars/examples/rust/main.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/madcars/examples/rust/main.rs b/madcars/examples/rust/main.rs index d19db03..02e0b31 100755 --- a/madcars/examples/rust/main.rs +++ b/madcars/examples/rust/main.rs @@ -30,8 +30,13 @@ fn run(mut strategy: T) { // Process the message let response = handle_message(msg, &mut strategy); + // Check whether there's a response + if response.is_none() { + continue; + } + // Encode response to JSON - let mut output = serde_json::to_string(&response).unwrap(); + let mut output = serde_json::to_string(&response.unwrap()).unwrap(); output.push_str("\n"); // Write response JSON to stdout @@ -40,9 +45,9 @@ fn run(mut strategy: T) { } /// Handle input message -fn handle_message(msg: Message, strategy: &mut T) -> Response { - // Handle each message type, produce command - let (cmd, debug_msg) = match msg.msg_type { +fn handle_message(msg: Message, strategy: &mut T) -> Option { + // Handle each message type, produce optional response + match msg.msg_type { MessageType::NewMatch => { // Extract message parameters let (my_lives, enemy_lives, proto_map, proto_car) = ( @@ -55,8 +60,8 @@ fn handle_message(msg: Message, strategy: &mut T) -> Response { // Call strategy's initialization function strategy.begin_match(my_lives, enemy_lives, proto_map, proto_car); - // Return 'stop' command and a debug message - (Command::Stop, "Starting new match!") + // This message shouldn't be answered + None } MessageType::Tick => { @@ -71,14 +76,11 @@ fn handle_message(msg: Message, strategy: &mut T) -> Response { let cmd = strategy.on_tick(my_car, enemy_car, deadline_pos); // Pass strategy's returned command plus a debug message - (cmd, "Drive ahead!!") + Some(Response { + command: cmd, + debug: Some("Drive ahead!!".to_string()), + }) } - }; - - // Create a response - Response { - command: cmd, - debug: Some(debug_msg.to_string()), } } From b10997f636e133e31280bfb217ede943b623ec7b Mon Sep 17 00:00:00 2001 From: Tongo Hiti Date: Sat, 1 Sep 2018 22:23:45 +0300 Subject: [PATCH 05/96] Emit all of three possible commands in sequence --- madcars/examples/rust/main.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/madcars/examples/rust/main.rs b/madcars/examples/rust/main.rs index 02e0b31..5f96182 100755 --- a/madcars/examples/rust/main.rs +++ b/madcars/examples/rust/main.rs @@ -244,10 +244,14 @@ mod strategy { // Print to console (stderr) eprintln!("Processing tick #{}", self.tick); - if (self.tick % 3) != 0 { + let t = self.tick % 9; + + if t < 4 { Command::Left - } else { + } else if t > 5 { Command::Right + } else { + Command::Stop } } } From e86c6e6f369a3a6ac76e938d94cb79cd89cd4a2c Mon Sep 17 00:00:00 2001 From: Tongo Hiti Date: Sat, 1 Sep 2018 22:24:36 +0300 Subject: [PATCH 06/96] Removed debug print to stderr - could crash strategy on server --- madcars/examples/rust/main.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/madcars/examples/rust/main.rs b/madcars/examples/rust/main.rs index 5f96182..b9798c6 100755 --- a/madcars/examples/rust/main.rs +++ b/madcars/examples/rust/main.rs @@ -241,9 +241,6 @@ mod strategy { fn on_tick(&mut self, _my_car: Car, _enemy_car: Car, _deadline_pos: f64) -> Command { self.tick += 1; - // Print to console (stderr) - eprintln!("Processing tick #{}", self.tick); - let t = self.tick % 9; if t < 4 { From 0b36dd72942ea26c54fafe6a68fa6861f8d8c07b Mon Sep 17 00:00:00 2001 From: Tongo Hiti Date: Sat, 1 Sep 2018 22:28:13 +0300 Subject: [PATCH 07/96] Commented out unused libraries in Cargo.toml --- madcars/examples/rust/Cargo.toml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/madcars/examples/rust/Cargo.toml b/madcars/examples/rust/Cargo.toml index 4fbafa1..ed00ce6 100755 --- a/madcars/examples/rust/Cargo.toml +++ b/madcars/examples/rust/Cargo.toml @@ -3,15 +3,15 @@ name = "strategy" version = "0.0.0" [dependencies] -chrono = "0.4.1" -fern = "0.5.5" -flexi_logger = "0.8.1" -lazy_static = "1.0.0" -log = "0.4.1" -num-traits = "0.2.2" -rand = "0.4.2" +#chrono = "0.4.1" +#fern = "0.5.5" +#flexi_logger = "0.8.1" +#lazy_static = "1.0.0" +#log = "0.4.1" +#num-traits = "0.2.2" +#rand = "0.4.2" serde = "1.0.13" serde_derive = "1.0.13" serde_json = "1.0.13" -simplelog = "0.5.1" -time = "0.1.39" +#simplelog = "0.5.1" +#time = "0.1.39" From 2a5e2d1c4bd7d13e1d188b98eeb037ed997d730e Mon Sep 17 00:00:00 2001 From: Alexey Date: Sun, 2 Sep 2018 00:03:07 +0300 Subject: [PATCH 08/96] Fix map names --- madcars/RULES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/madcars/RULES.md b/madcars/RULES.md index dc84929..c54aba7 100644 --- a/madcars/RULES.md +++ b/madcars/RULES.md @@ -347,7 +347,7 @@ while True: * `--fpl` — путь к логам первого игрока * `-s` или `--sp` — **команда для запуска исполняемого файла стратегии второго игрока** * `--spl` — путь к логам второго игрока -* `-m` или `--matches` — список пар (карта, машина) для игровых матчей (`-m PillHill,Bus PillHill,Buggy PillHubble,Buggy`) +* `-m` или `--matches` — список пар (карта, машина) для игровых матчей (`-m PillHillMap,Bus PillHillMap,Buggy PillHubbleMap,Buggy`) Таким образом, примеры запуска при установленном python3 и зависимостями: * ```$ python3 localrunner.py``` — запуск с управлением с клавиатуры From 343c49ea42f41332d073384367eb6807a2372527 Mon Sep 17 00:00:00 2001 From: Tongo Hiti Date: Sun, 2 Sep 2018 07:07:47 +0300 Subject: [PATCH 09/96] Fixed 'drive' parameter data type --- madcars/examples/rust/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/madcars/examples/rust/main.rs b/madcars/examples/rust/main.rs index b9798c6..d6ecd1c 100755 --- a/madcars/examples/rust/main.rs +++ b/madcars/examples/rust/main.rs @@ -149,7 +149,7 @@ mod model { pub car_body_elasticity: f64, pub max_speed: f64, pub max_angular_speed: f64, - pub drive: f64, + pub drive: u32, // Drive type: 1=FF, 2=FR, 3=AWD pub rear_wheel_mass: f64, pub rear_wheel_position: Point2D, From 01457dff12d42a1572a07f337d4199b53bba91c1 Mon Sep 17 00:00:00 2001 From: Zhan Poplavskyi Date: Sun, 2 Sep 2018 14:14:41 +0300 Subject: [PATCH 10/96] Add Gson dependency to pom.xml --- madcars/dockers/java1.8/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/madcars/dockers/java1.8/pom.xml b/madcars/dockers/java1.8/pom.xml index 8df58bf..9802c3f 100644 --- a/madcars/dockers/java1.8/pom.xml +++ b/madcars/dockers/java1.8/pom.xml @@ -42,6 +42,12 @@ json 20180130 + + + com.google.code.gson + gson + 2.8.5 + From 99d86e6ca98a59d630b774a2f4f963c427062760 Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Sun, 2 Sep 2018 16:33:57 +0300 Subject: [PATCH 11/96] readtimeout fix --- madcars/Runners/mechanic/player.py | 5 +++++ madcars/Runners/mechanic/strategy.py | 7 +++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/madcars/Runners/mechanic/player.py b/madcars/Runners/mechanic/player.py index 97b1d54..a833925 100644 --- a/madcars/Runners/mechanic/player.py +++ b/madcars/Runners/mechanic/player.py @@ -42,6 +42,11 @@ def apply_turn(self, tick): elif turn == 'right': self.car.go_right() except Exception as e: + args = e.args + if len(args) > 0: + self.debug_log.append({'tick': tick, 'message': args[0]}) + else: + self.debug_log.append({'tick': tick, 'message': str(e)}) print('read exception', self.client.get_solution_id(), e) self.is_disconnected = True self.client.close() diff --git a/madcars/Runners/mechanic/strategy.py b/madcars/Runners/mechanic/strategy.py index e158590..f359daf 100644 --- a/madcars/Runners/mechanic/strategy.py +++ b/madcars/Runners/mechanic/strategy.py @@ -147,10 +147,9 @@ def get_command(self): raise ConnectionError('Connection closed') self.execution_time += (datetime.datetime.now() - before) if self.execution_time > self.EXECUTION_LIMIT: - return {'debug': 'sum timeout'} - except asyncio.TimeoutError as e: - return {'debug': 'read timeout error'} - + raise Exception('sum timeout error') + except asyncio.TimeoutError: + raise asyncio.TimeoutError('read timeout error') try: z = json.loads(z.decode()) except ValueError: From e00e2f7a418b89767bd3599b18f14b5c7aadab35 Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Sun, 2 Sep 2018 17:53:18 +0300 Subject: [PATCH 12/96] add torque to car body --- madcars/RULES.md | 3 ++- madcars/Runners/mechanic/game.py | 1 + madcars/Runners/mechanic/game_objects/base_car.py | 6 ++++-- madcars/Runners/mechanic/game_objects/cars.py | 2 ++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/madcars/RULES.md b/madcars/RULES.md index c54aba7..577901f 100644 --- a/madcars/RULES.md +++ b/madcars/RULES.md @@ -276,7 +276,8 @@ while True: * `car_body_friction` - трение кузова машины * `car_body_elasticity` - эластичность кузова машины * `max_speed` - максимальная угловая скорость колес - * `max_angular_speed` - максимальная угловая скорость в воздухе + * `max_angular_speed` - максимальная угловая скорость в воздухе (данный параметр больше не используется и скоро будет удален) + * `torque` - крутящий момент кузова машины в воздухе * `drive` - привод машины (передний, задний, полный) * `rear_wheel_radius` - радиус заднего колеса diff --git a/madcars/Runners/mechanic/game.py b/madcars/Runners/mechanic/game.py index 58c8114..52b704f 100644 --- a/madcars/Runners/mechanic/game.py +++ b/madcars/Runners/mechanic/game.py @@ -55,6 +55,7 @@ def __init__(self, clients, games_list, extended_save=True): self.space = pymunk.Space() self.space.gravity = (0.0, -700) + self.space.damping = 0.85 self.scores = defaultdict(int) self.matches = self.parse_games(games_list) self.current_match = None diff --git a/madcars/Runners/mechanic/game_objects/base_car.py b/madcars/Runners/mechanic/game_objects/base_car.py index 6e97845..a7b0b2d 100644 --- a/madcars/Runners/mechanic/game_objects/base_car.py +++ b/madcars/Runners/mechanic/game_objects/base_car.py @@ -26,6 +26,7 @@ class Car(object): max_speed = 300 max_angular_speed = 2 + torque = 20000000 drive = FR rear_wheel_mass = 60 @@ -173,14 +174,14 @@ def get_objects_for_space_at(self, point): def go_right(self): if self.in_air(): - self.car_body.angular_velocity = self.max_angular_speed + self.car_body.torque = self.torque for motor in self.motors: motor.rate = -self.max_speed def go_left(self): if self.in_air(): - self.car_body.angular_velocity = self.max_angular_speed * -1 + self.car_body.torque = -self.torque for motor in self.motors: motor.rate = self.max_speed @@ -220,6 +221,7 @@ def proto_dump(cls, visio=False): 'car_body_elasticity': cls.car_body_elasticity, 'max_speed': cls.max_speed, 'max_angular_speed': cls.max_angular_speed, + 'torque': cls.torque, 'drive': cls.drive, 'rear_wheel_mass': cls.rear_wheel_mass, diff --git a/madcars/Runners/mechanic/game_objects/cars.py b/madcars/Runners/mechanic/game_objects/cars.py index 07f8e79..5fd068e 100644 --- a/madcars/Runners/mechanic/game_objects/cars.py +++ b/madcars/Runners/mechanic/game_objects/cars.py @@ -22,6 +22,7 @@ class Buggy(Car): button_hw = (1, 38) max_speed = 70 + torque = 14000000 drive = Car.FR @@ -61,6 +62,7 @@ class Bus(Car): button_hw = (1, 28) max_speed = 45 + torque = 35000000 drive = Car.AWD From ee7b4e6424b2fe9bb271117bf6d5f23677456dd8 Mon Sep 17 00:00:00 2001 From: Nirenamid Date: Sun, 2 Sep 2018 18:32:32 +0300 Subject: [PATCH 13/96] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Базовый пример на Java с парсингом JSON-данных тика (данные матча не реализованы) --- madcars/examples/Java/Bot.java | 9 +++ madcars/examples/Java/JsonIO.java | 55 +++++++++++++++ madcars/examples/Java/Main.java | 94 ++++++++++++++++++++++++++ madcars/examples/Java/MatchConfig.java | 19 ++++++ madcars/examples/Java/TickState.java | 54 +++++++++++++++ 5 files changed, 231 insertions(+) create mode 100644 madcars/examples/Java/Bot.java create mode 100644 madcars/examples/Java/JsonIO.java create mode 100644 madcars/examples/Java/Main.java create mode 100644 madcars/examples/Java/MatchConfig.java create mode 100644 madcars/examples/Java/TickState.java diff --git a/madcars/examples/Java/Bot.java b/madcars/examples/Java/Bot.java new file mode 100644 index 0000000..afa44e9 --- /dev/null +++ b/madcars/examples/Java/Bot.java @@ -0,0 +1,9 @@ +/** + * Интерфейс типового робота, получающего на вход конфигурацию и состояние мира в формате JSON, + * и отправляющего на выход своё решение на текущем шаге (тоже в JSON) * + */ +public interface Bot { + void onMatchStarted(MatchConfig matchConfig); + void onNextTick(TickState tickState); + void onParsingError(String message); +} diff --git a/madcars/examples/Java/JsonIO.java b/madcars/examples/Java/JsonIO.java new file mode 100644 index 0000000..fe67ad4 --- /dev/null +++ b/madcars/examples/Java/JsonIO.java @@ -0,0 +1,55 @@ +import org.json.JSONObject; +import java.io.*; + +/** + * Обертка, позволяющая считывать из потока ввода и писать в поток вывода JSON-объекты + * Для отладочных целей есть возможность читать команды не из STDIN, а из файла, например: + * + *

{@code gameMessage = JsonIO.readFromFile("messages.json")}

+ */ + +public class JsonIO { + private static FileInputStream fileStream; + private static InputStreamReader reader; + private static BufferedReader bufferedReader; + + public static JSONObject readFromStdIn(){ + return readFromStream(System.in); + } + + public static JSONObject readFromFile(String fileName){ + try { + if (fileStream == null) + fileStream = new FileInputStream(fileName); + return readFromStream(fileStream); + } + catch (FileNotFoundException e) { + return readFromStdIn(); + } + } + private static JSONObject readFromStream(InputStream stream){ + if (reader == null) + reader = new InputStreamReader(stream); + + if (bufferedReader == null) + bufferedReader = new BufferedReader(reader); + + try { + String line = bufferedReader.readLine(); + return (line != null && line.length() != 0) + ? new JSONObject(line) + : null; + } + catch (IOException e) { + return null; + } + } + + public static void writeToStdOut(JSONObject object){ + writeToStream(System.out, object); + } + + private static void writeToStream(PrintStream stream, JSONObject object) { + stream.println(object.toString()); + } +} diff --git a/madcars/examples/Java/Main.java b/madcars/examples/Java/Main.java new file mode 100644 index 0000000..2f0ee42 --- /dev/null +++ b/madcars/examples/Java/Main.java @@ -0,0 +1,94 @@ +import org.json.JSONObject; +import java.util.Random; + +public class Main { + private static Bot robot; + + public static void main(String[] args) { + + JSONObject gameMessage; + while ((gameMessage = JsonIO.readFromStdIn()) != null) { + MessageType messageType; + try { + messageType = gameMessage.getEnum(MessageType.class, "type"); + switch (messageType) { + case tick: + TickState tickState = new TickState(gameMessage.getJSONObject("params")); + robot().onNextTick(tickState); + break; + + case new_match: + MatchConfig matchConfig = new MatchConfig(gameMessage.getJSONObject("params")); + robot().onMatchStarted(matchConfig); + break; + } + } + catch (Exception e){ + robot().onParsingError(e.getMessage()); + } + } + } + + private static Bot robot() { + + if (robot == null) + robot = new Bot() { + // todo заменить этот анонимный класс-заглушку реальным классом стратегии + static final int ON_AIR_PAUSE = 50; + final String commands[] = {"left", "stop", "right"}; + + int thisMathTick = 0; + int matchCounter = 0; + String debugMessage = ""; + + @Override + public void onMatchStarted(MatchConfig matchConfig){ + thisMathTick = 0; + matchCounter ++; + + debugMessage = String.format(".... Match #%d: lives=%d, ", matchCounter, matchConfig.myLives); + } + + @Override + public void onNextTick(TickState tickState) { + thisMathTick++; + + if (thisMathTick == 1) + debugMessage += String.format("my side=%s.... ", commands[1 - tickState.myCar.mirror]); + + String cmd = thisMathTick <= ON_AIR_PAUSE ? "stop" : commands[new Random().nextInt(3)]; + debugMessage += String.format("%d.%d: %s",matchCounter, thisMathTick, cmd); + + new Answer(cmd, debugMessage).send(); + debugMessage = ""; + } + + @Override + public void onParsingError(String message) { + debugMessage = message; + } + }; + return robot; + } + + public static class Answer { + String command; + String debug; + + public String getCommand() { return command;} + public String getDebug() { return debug;} + + Answer(String cmd, String dbg){ + command = cmd; + debug = dbg; + } + void send(){ + JSONObject json = new JSONObject(this); + JsonIO.writeToStdOut(json); + } + } + enum MessageType { + new_match, + tick + } +} diff --git a/madcars/examples/Java/MatchConfig.java b/madcars/examples/Java/MatchConfig.java new file mode 100644 index 0000000..a920ef1 --- /dev/null +++ b/madcars/examples/Java/MatchConfig.java @@ -0,0 +1,19 @@ +import org.json.JSONObject; + +/** + * Параметры матча (характеристики машин, контуры карт и т.д.), присылаемые в начале каждого матча. + * Передается на вход обработчика {@code onMatchStarted} интерфейса {@link Bot} + */ + +public class MatchConfig { + //todo добавить нужные поля и классы и реализовать десериализацию json-объекта + int myLives; + int enemyLives; + // ... + + public MatchConfig(JSONObject params) { + myLives = params.getInt("my_lives"); + enemyLives = params.getInt("enemy_lives"); + // ... + } +} diff --git a/madcars/examples/Java/TickState.java b/madcars/examples/Java/TickState.java new file mode 100644 index 0000000..51221fe --- /dev/null +++ b/madcars/examples/Java/TickState.java @@ -0,0 +1,54 @@ +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * Состояние мира, присылаемое сервером на каждом тике. + * Передается на вход обработчика {@code onNextTick} интерфейса {@link Bot} + */ + +class TickState { + Car myCar; + Car enemyCar; + float deadLine; + + public TickState(JSONObject params){ + myCar = new Car(params.getJSONArray("my_car")); + enemyCar = new Car(params.getJSONArray("enemy_car")); + + deadLine = params.getFloat("deadline_position"); + } + + class Car { + int mirror; // слева = +1, справа = -1 + + float x, y; + float angle; + + WheelPair wheel = new WheelPair(); + + Car(JSONArray carParam){ + JSONArray pos = carParam.getJSONArray(0); + x = pos.getFloat(0); + y = pos.getFloat(1); + + angle = carParam.getFloat(1); + mirror = carParam.getInt(2); + + wheel.rear = new Wheel(carParam.getJSONArray(3)); + wheel.front = new Wheel(carParam.getJSONArray(4)); + } + class WheelPair { + Wheel rear; + Wheel front; + } + class Wheel { + float x, y; + float angle; + Wheel (JSONArray wheelParam) { + x = wheelParam.getFloat(0); + y = wheelParam.getFloat(1); + angle = wheelParam.getFloat(2); + } + } + } +} \ No newline at end of file From 2ae7a779b17a8a1c913be3cdea71fdd6ca3b85db Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 3 Sep 2018 01:21:31 +0300 Subject: [PATCH 14/96] gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 056c5fc..1e40a71 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.pro.user *.pro.user.* *.pyc +.idea/workspace.xml From dc0ccafb05ae389170101e7ca1a08a9a4f4700c2 Mon Sep 17 00:00:00 2001 From: Tongo Hiti Date: Mon, 3 Sep 2018 20:14:48 +0300 Subject: [PATCH 15/96] Adjusting json model - replaced max_angular_speed with torque --- madcars/examples/rust/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/madcars/examples/rust/main.rs b/madcars/examples/rust/main.rs index d6ecd1c..2b07e05 100755 --- a/madcars/examples/rust/main.rs +++ b/madcars/examples/rust/main.rs @@ -148,7 +148,7 @@ mod model { pub car_body_friction: f64, pub car_body_elasticity: f64, pub max_speed: f64, - pub max_angular_speed: f64, + pub torque: f64, pub drive: u32, // Drive type: 1=FF, 2=FR, 3=AWD pub rear_wheel_mass: f64, From c361e997866b3dba878d197e4dfc8bb24a3419b5 Mon Sep 17 00:00:00 2001 From: Dmitry Sannikov Date: Tue, 4 Sep 2018 14:25:38 +0300 Subject: [PATCH 16/96] Update RULES.md --- madcars/RULES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/madcars/RULES.md b/madcars/RULES.md index 577901f..c718ab2 100644 --- a/madcars/RULES.md +++ b/madcars/RULES.md @@ -246,6 +246,8 @@ base_arcs = [ ### 2.1. Архитектура решения Самое простое решение на языке программирования Python 3 будет состоять из одного файла `main.py`. Клиент инициирует сессию и запускает этот скрипт в начале работы. Клиент скрывает внутри себя работу с сетью и обработку ошибок, и является посредником между игровой механикой и решением участника. Он передает JSON от мира в стандартный поток ввода (`stdin`) и забирает ответ из стандартного потока вывода (`stdout`) (для Python это `sys.stdin` и `sys.stdout`). Напомним, что для корректной работы решение должно запускаться с параметрами для ввода/вывода без буфера (для Python это флаг `-u` при запуске интерпретатора). +Максимальный размер файла стратегии — `20` Мб. + Решение стартует один раз и далее работает в бесконечном цикле. Обрабатывать остановку не обязательно, в конце игры `dockerd` завершит работу всех контейнеров. Внутри цикла заключается обработка ввода/вывода, происходит обработка JSON и вызов главного обработчика (пример простой стратегии на Python): ```python @@ -378,7 +380,7 @@ Local Runner поддерживает переопределение парам Пользователи Windows могут столкнуться с проблемой, когда интерпретатор языка программирования установлен не совсем верно, и его нет в системной переменной `PATH`. В такому случае, необходимо указывать полный путь к интерпретатору любимого языка, или добавлять его в `PATH`. ### 4. Отладочный просмотр на сайте -На сайте чемпионата предусмотрен отладочный вывод. После того, когда игра с соперником будет проиграна, вы сможете посмотреть вывод команд вашей стратегии в консоли браузера. Для этого вам надо нажать на кнопку `Отладочный просмотр` на странице игры. +На сайте чемпионата предусмотрен отладочный вывод. После того, когда игра с соперником будет завершена, вы сможете посмотреть вывод команд вашей стратегии в консоли браузера. Для этого вам надо нажать на кнопку `Отладочный просмотр` на странице игры. Пример на рисунке. ![Отладочный вывод на сайте](schemes/debugOnSite.png) From f98a29176369ebd19078d93ff44e6ae95f6da82e Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Tue, 4 Sep 2018 23:50:11 +0300 Subject: [PATCH 17/96] add groove joint and py3.6 --- .../Runners/mechanic/game_objects/base_car.py | 16 ++++++---------- madcars/Runners/mechanic/game_objects/cars.py | 19 ++++++------------- madcars/Runners/mechanic/game_objects/maps.py | 11 +++++++---- madcars/dockers/python3/Dockerfile | 6 ++++-- 4 files changed, 23 insertions(+), 29 deletions(-) diff --git a/madcars/Runners/mechanic/game_objects/base_car.py b/madcars/Runners/mechanic/game_objects/base_car.py index a7b0b2d..c0df20c 100644 --- a/madcars/Runners/mechanic/game_objects/base_car.py +++ b/madcars/Runners/mechanic/game_objects/base_car.py @@ -84,7 +84,6 @@ def create_wheel(self, wheel_side): wheel_position = getattr(self, wheel_side + '_wheel_position') wheel_friction = getattr(self, wheel_side + '_wheel_friction') wheel_elasticity = getattr(self, wheel_side + '_wheel_elasticity') - wheel_joint = getattr(self, wheel_side + '_wheel_joint') wheel_damp_position = getattr(self, wheel_side + '_wheel_damp_position') wheel_damp_length = getattr(self, wheel_side + '_wheel_damp_length') wheel_damp_stiffness = getattr(self, wheel_side + '_wheel_damp_stiffness') @@ -100,8 +99,12 @@ def create_wheel(self, wheel_side): wheel_shape.elasticity = wheel_elasticity wheel_objects.append(wheel_shape) - wheel_joint = pymunk.PinJoint(wheel_body, self.car_body, anchor_b=(wheel_joint[0] * self.x_modification, wheel_joint[1])) - wheel_objects.append(wheel_joint) + wheel_grove = pymunk.GrooveJoint(self.car_body, wheel_body, + (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1]), + (wheel_damp_position[0] * self.x_modification, + wheel_damp_position[1] - wheel_damp_length * 1.5), + (0, 0)) + wheel_objects.append(wheel_grove) wheel_damp = pymunk.DampedSpring(wheel_body, self.car_body, anchor_a=(0, 0), anchor_b=(wheel_damp_position[0] * self.x_modification, wheel_damp_position[1]), @@ -110,16 +113,9 @@ def create_wheel(self, wheel_side): damping=wheel_damp_damping) wheel_objects.append(wheel_damp) - wheel_stop = pymunk.Poly(self.car_body, [(0, 0), (0, 1), (wheel_radius * 2 * self.x_modification, 1), (wheel_radius * 2 * self.x_modification, 0)], - transform=pymunk.Transform(tx=wheel_damp_position[0] * self.x_modification - wheel_radius * self.x_modification, ty=wheel_damp_position[1])) - wheel_objects.append(wheel_stop) - - wheel_stop.color = 0, 255, 0 - wheel_motor = None if (wheel_side == 'rear' and self.drive in [self.AWD, self.FR]) or (wheel_side == 'front' and self.drive in [self.AWD, self.FF]): wheel_motor = pymunk.SimpleMotor(wheel_body, self.car_body, 0) - # wheel_objects.append(motor) return wheel_body, wheel_motor, wheel_objects diff --git a/madcars/Runners/mechanic/game_objects/cars.py b/madcars/Runners/mechanic/game_objects/cars.py index 5fd068e..59eb29d 100644 --- a/madcars/Runners/mechanic/game_objects/cars.py +++ b/madcars/Runners/mechanic/game_objects/cars.py @@ -28,7 +28,6 @@ class Buggy(Car): rear_wheel_mass = 50 rear_wheel_position = (29, -5) - rear_wheel_joint = (150, 0) rear_wheel_damp_position = (29, 20) rear_wheel_damp_stiffness = 5e4 rear_wheel_damp_damping = 3e3 @@ -37,7 +36,6 @@ class Buggy(Car): front_wheel_mass = 5 front_wheel_position = (122, -5) - front_wheel_joint = (0, 6) front_wheel_damp_position = (122, 20) front_wheel_damp_length = 25 front_wheel_radius = 12 @@ -69,7 +67,6 @@ class Bus(Car): rear_wheel_radius = 13 rear_wheel_position = (38, -5) rear_wheel_friction = 0.9 - rear_wheel_joint = (153, 5) rear_wheel_damp_position = (38, 30) rear_wheel_damp_length = 35 rear_wheel_damp_stiffness = 10e4 @@ -77,7 +74,6 @@ class Bus(Car): front_wheel_radius = 13 front_wheel_position = (125, -5) - front_wheel_joint = (0, 6) front_wheel_damp_position = (125, 30) front_wheel_damp_length = 35 front_wheel_damp_stiffness = 10e4 @@ -108,7 +104,6 @@ def create_wheel(self, wheel_side): wheel_position = getattr(self, wheel_side + '_wheel_position') wheel_friction = getattr(self, wheel_side + '_wheel_friction') wheel_elasticity = getattr(self, wheel_side + '_wheel_elasticity') - wheel_joint = getattr(self, wheel_side + '_wheel_joint') wheel_damp_position = getattr(self, wheel_side + '_wheel_damp_position') wheel_damp_length = getattr(self, wheel_side + '_wheel_damp_length') wheel_damp_stiffness = getattr(self, wheel_side + '_wheel_damp_stiffness') @@ -124,8 +119,12 @@ def create_wheel(self, wheel_side): wheel_shape.elasticity = wheel_elasticity wheel_objects.append(wheel_shape) - wheel_joint = pymunk.PinJoint(wheel_body, self.car_body, anchor_b=(wheel_joint[0] * self.x_modification, wheel_joint[1])) - wheel_objects.append(wheel_joint) + wheel_grove = pymunk.GrooveJoint(self.car_body, wheel_body, + (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1]), + (wheel_damp_position[0] * self.x_modification, + wheel_damp_position[1] - wheel_damp_length * 1.5), + (0, 0)) + wheel_objects.append(wheel_grove) wheel_damp = pymunk.DampedSpring(wheel_body, self.car_body, anchor_a=(0, 0), anchor_b=(wheel_damp_position[0] * self.x_modification, wheel_damp_position[1]), @@ -134,12 +133,6 @@ def create_wheel(self, wheel_side): damping=wheel_damp_damping) wheel_objects.append(wheel_damp) - wheel_stop = pymunk.Poly(self.car_body, [(0, 0), (0, 1),(wheel_radius * 2 * self.x_modification, 1), (wheel_radius * 2 * self.x_modification, 0)], - transform=pymunk.Transform(tx=wheel_damp_position[0] * self.x_modification - wheel_radius * self.x_modification, ty=wheel_damp_position[1])) - wheel_objects.append(wheel_stop) - - wheel_stop.color = 0, 255, 0 - wheel_motor = None if (wheel_side == 'rear' and self.drive in [self.AWD, self.FR]) or (wheel_side == 'front' and self.drive in [self.AWD, self.FF]): wheel_motor = pymunk.SimpleMotor(wheel_body, self.car_body, 0) diff --git a/madcars/Runners/mechanic/game_objects/maps.py b/madcars/Runners/mechanic/game_objects/maps.py index 979bb0d..c23966e 100644 --- a/madcars/Runners/mechanic/game_objects/maps.py +++ b/madcars/Runners/mechanic/game_objects/maps.py @@ -32,16 +32,19 @@ def __init__(self, space): self.objects.append(segment) def create_box(self, space): - left = pymunk.Segment(space.static_body, (0, 0), (0, self.max_height), 1) + bo = self.segment_height - 1 # box offset + left = pymunk.Segment(space.static_body, (-bo, -bo), (-bo, self.max_height + bo), self.segment_height) left.sensor = True - top = pymunk.Segment(space.static_body, (0, self.max_height), (self.max_width, self.max_height), 1) + top = pymunk.Segment(space.static_body, (-bo, self.max_height + bo), + (self.max_width + bo, self.max_height + bo), 10) top.sensor = True - right = pymunk.Segment(space.static_body, (self.max_width, self.max_height), (self.max_width, 0), 1) + right = pymunk.Segment(space.static_body, (self.max_width + bo, self.max_height + bo), + (self.max_width + bo, -bo), 10) right.sensor = True - bottom = pymunk.Segment(space.static_body, (self.max_width, 0), (0, 0), 1) + bottom = pymunk.Segment(space.static_body, (self.max_width + bo, -bo), (-bo, -bo), 10) bottom.sensor = True self.objects.extend([left, top, right, bottom]) diff --git a/madcars/dockers/python3/Dockerfile b/madcars/dockers/python3/Dockerfile index 87c2d4b..70c69fa 100644 --- a/madcars/dockers/python3/Dockerfile +++ b/madcars/dockers/python3/Dockerfile @@ -1,8 +1,10 @@ FROM ubuntu:16.04 MAINTAINER Boris Kolganov -RUN apt-get update && apt-get install -y python3 python3-pip && pip3 install -U numpy scipy cython scikit-learn keras pandas tensorflow==1.5.0 pymunk==5.3.2 +RUN add-apt-repository ppa:jonathonf/python-3.6 && \ + apt-get update && apt-get install -y python3.6 python3-pip && \ + python3.6 -m pip install -U numpy scipy cython scikit-learn keras pandas tensorflow==1.5.0 pymunk==5.3.2 ENV SOLUTION_CODE_ENTRYPOINT=main.py ENV SOLUTION_CODE_PATH=/opt/client/solution -ENV RUN_COMMAND='python3 -u $SOLUTION_CODE_PATH/$SOLUTION_CODE_ENTRYPOINT' +ENV RUN_COMMAND='python3.6 -u $SOLUTION_CODE_PATH/$SOLUTION_CODE_ENTRYPOINT' From a7987c87b5aab1cf3df8f355d754fe609f27e4df Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Thu, 6 Sep 2018 00:22:22 +0300 Subject: [PATCH 18/96] mechanic fixes --- madcars/Runners/mechanic/game_objects/base_car.py | 5 ++++- madcars/Runners/mechanic/game_objects/cars.py | 3 ++- madcars/Runners/mechanic/game_objects/maps.py | 6 +++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/madcars/Runners/mechanic/game_objects/base_car.py b/madcars/Runners/mechanic/game_objects/base_car.py index c0df20c..95d04ca 100644 --- a/madcars/Runners/mechanic/game_objects/base_car.py +++ b/madcars/Runners/mechanic/game_objects/base_car.py @@ -35,6 +35,7 @@ class Car(object): rear_wheel_friction = 1 rear_wheel_elasticity = 0.8 rear_wheel_joint = (0, 0) + rear_wheel_groove_offset = 5 rear_wheel_damp_position = (0, 0) rear_wheel_damp_length = 20 rear_wheel_damp_stiffness = 6e4 @@ -46,6 +47,7 @@ class Car(object): front_wheel_friction = 1 front_wheel_elasticity = 0.8 front_wheel_joint = (0, 0) + front_wheel_groove_offset = 5 front_wheel_damp_position = (0, 0) front_wheel_damp_length = 20 front_wheel_damp_stiffness = 6e4 @@ -84,6 +86,7 @@ def create_wheel(self, wheel_side): wheel_position = getattr(self, wheel_side + '_wheel_position') wheel_friction = getattr(self, wheel_side + '_wheel_friction') wheel_elasticity = getattr(self, wheel_side + '_wheel_elasticity') + wheel_grove_offset = getattr(self, wheel_side + '_wheel_groove_offset') wheel_damp_position = getattr(self, wheel_side + '_wheel_damp_position') wheel_damp_length = getattr(self, wheel_side + '_wheel_damp_length') wheel_damp_stiffness = getattr(self, wheel_side + '_wheel_damp_stiffness') @@ -100,7 +103,7 @@ def create_wheel(self, wheel_side): wheel_objects.append(wheel_shape) wheel_grove = pymunk.GrooveJoint(self.car_body, wheel_body, - (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1]), + (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1] - wheel_grove_offset), (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1] - wheel_damp_length * 1.5), (0, 0)) diff --git a/madcars/Runners/mechanic/game_objects/cars.py b/madcars/Runners/mechanic/game_objects/cars.py index 59eb29d..50f1a40 100644 --- a/madcars/Runners/mechanic/game_objects/cars.py +++ b/madcars/Runners/mechanic/game_objects/cars.py @@ -104,6 +104,7 @@ def create_wheel(self, wheel_side): wheel_position = getattr(self, wheel_side + '_wheel_position') wheel_friction = getattr(self, wheel_side + '_wheel_friction') wheel_elasticity = getattr(self, wheel_side + '_wheel_elasticity') + wheel_grove_offset = getattr(self, wheel_side + '_wheel_groove_offset') wheel_damp_position = getattr(self, wheel_side + '_wheel_damp_position') wheel_damp_length = getattr(self, wheel_side + '_wheel_damp_length') wheel_damp_stiffness = getattr(self, wheel_side + '_wheel_damp_stiffness') @@ -120,7 +121,7 @@ def create_wheel(self, wheel_side): wheel_objects.append(wheel_shape) wheel_grove = pymunk.GrooveJoint(self.car_body, wheel_body, - (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1]), + (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1] - wheel_grove_offset), (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1] - wheel_damp_length * 1.5), (0, 0)) diff --git a/madcars/Runners/mechanic/game_objects/maps.py b/madcars/Runners/mechanic/game_objects/maps.py index c23966e..a80389b 100644 --- a/madcars/Runners/mechanic/game_objects/maps.py +++ b/madcars/Runners/mechanic/game_objects/maps.py @@ -37,14 +37,14 @@ def create_box(self, space): left.sensor = True top = pymunk.Segment(space.static_body, (-bo, self.max_height + bo), - (self.max_width + bo, self.max_height + bo), 10) + (self.max_width + bo, self.max_height + bo), self.segment_height) top.sensor = True right = pymunk.Segment(space.static_body, (self.max_width + bo, self.max_height + bo), - (self.max_width + bo, -bo), 10) + (self.max_width + bo, -bo), self.segment_height) right.sensor = True - bottom = pymunk.Segment(space.static_body, (self.max_width + bo, -bo), (-bo, -bo), 10) + bottom = pymunk.Segment(space.static_body, (self.max_width + bo, -bo), (-bo, -bo), self.segment_height) bottom.sensor = True self.objects.extend([left, top, right, bottom]) From 67b7df8d0d0ddacb7b213a534442f10b3cee2f66 Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Thu, 6 Sep 2018 00:35:55 +0300 Subject: [PATCH 19/96] add scale to lr --- madcars/Runners/localrunner.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/madcars/Runners/localrunner.py b/madcars/Runners/localrunner.py index 560e1e3..0f40be9 100644 --- a/madcars/Runners/localrunner.py +++ b/madcars/Runners/localrunner.py @@ -9,9 +9,11 @@ from mechanic.game import Game from mechanic.strategy import KeyboardClient, FileClient -window = pyglet.window.Window(1200, 800, vsync=False) -draw_options = pymunk.pyglet_util.DrawOptions() -_ = pyglet.clock.ClockDisplay(interval=0.016) + +maps = ['PillMap', 'PillHubbleMap', 'PillHillMap', 'PillCarcassMap', 'IslandMap', 'IslandHoleMap'] +cars = ['Buggy', 'Bus', 'SquareWheelsBuggy'] + +games = [','.join(t) for t in product(maps, cars)] parser = argparse.ArgumentParser(description='LocalRunner for MadCars') @@ -24,16 +26,20 @@ help='Path to executable with strategy for second player', default='keyboard') parser.add_argument('--spl', type=str, nargs='?', help='Path to log for second player') +parser.add_argument('-m', '--matches', nargs='+', help='List of pairs(map, car) for games', default=games) -maps = ['PillMap', 'PillHubbleMap', 'PillHillMap', 'PillCarcassMap', 'IslandMap', 'IslandHoleMap'] -cars = ['Buggy', 'Bus', 'SquareWheelsBuggy'] -games = [','.join(t) for t in product(maps, cars)] - +parser.add_argument('-S', '--scale', type=float, help='Window scale', default=1) -parser.add_argument('-m', '--matches', nargs='+', help='List of pairs(map, car) for games', default=games) args = parser.parse_args() +scale = args.scale + +window = pyglet.window.Window(1200 * scale, 800 * scale, vsync=False) +draw_options = pymunk.pyglet_util.DrawOptions() +_ = pyglet.clock.ClockDisplay(interval=0.016) +pyglet.gl.glScalef(scale, scale, scale) + first_player = args.fp second_player = args.sp @@ -55,10 +61,9 @@ @window.event def on_draw(): - pyglet.gl.glClearColor(255,255,255,255) + pyglet.gl.glClearColor(255, 255, 255, 255) window.clear() game.draw(draw_options) - game.tick() if not game.game_complete: future_message = loop.run_until_complete(game.tick()) else: From 456868ff2f69173c2d217950dc594703d39001cb Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Thu, 6 Sep 2018 01:06:58 +0300 Subject: [PATCH 20/96] add groove to json --- madcars/Runners/mechanic/game_objects/base_car.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/madcars/Runners/mechanic/game_objects/base_car.py b/madcars/Runners/mechanic/game_objects/base_car.py index 95d04ca..394ec23 100644 --- a/madcars/Runners/mechanic/game_objects/base_car.py +++ b/madcars/Runners/mechanic/game_objects/base_car.py @@ -228,6 +228,7 @@ def proto_dump(cls, visio=False): 'rear_wheel_friction': cls.rear_wheel_friction, 'rear_wheel_elasticity': cls.rear_wheel_elasticity, 'rear_wheel_joint': cls.rear_wheel_joint, + 'rear_wheel_groove_offset': cls.rear_wheel_groove_offset, 'rear_wheel_damp_position': cls.rear_wheel_damp_position, 'rear_wheel_damp_length': cls.rear_wheel_damp_length, 'rear_wheel_damp_stiffness': cls.rear_wheel_damp_stiffness, @@ -238,6 +239,7 @@ def proto_dump(cls, visio=False): 'front_wheel_friction': cls.front_wheel_friction, 'front_wheel_elasticity': cls.front_wheel_elasticity, 'front_wheel_joint': cls.front_wheel_joint, + 'front_wheel_groove_offset': cls.front_wheel_groove_offset, 'front_wheel_damp_position': cls.front_wheel_damp_position, 'front_wheel_damp_length': cls.front_wheel_damp_length, 'front_wheel_damp_stiffness': cls.front_wheel_damp_stiffness, From fc0af0d3443df6c4eef45ade9c35ad28f170cfc5 Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Thu, 6 Sep 2018 01:08:24 +0300 Subject: [PATCH 21/96] small fix to dockerfile --- madcars/Runners/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/madcars/Runners/Dockerfile b/madcars/Runners/Dockerfile index 50786e6..c527f68 100644 --- a/madcars/Runners/Dockerfile +++ b/madcars/Runners/Dockerfile @@ -9,8 +9,9 @@ RUN apt-get update && \ apt-get autoclean && \ apt-get autoremove -COPY . ./ +COPY requirements.txt ./requirements.txt RUN pip3 install -r requirements.txt +COPY . ./ EXPOSE 8000 CMD ["python3", "-u", "./serverrunner.py"] \ No newline at end of file From 69ca6744b908e20ff051b01907792d2698931732 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 6 Sep 2018 01:31:22 +0300 Subject: [PATCH 22/96] match_params_example.json Exapmple --- madcars/Runners/match_params_example.json | 791 ++++++++++++++++++++++ 1 file changed, 791 insertions(+) create mode 100644 madcars/Runners/match_params_example.json diff --git a/madcars/Runners/match_params_example.json b/madcars/Runners/match_params_example.json new file mode 100644 index 0000000..80c7938 --- /dev/null +++ b/madcars/Runners/match_params_example.json @@ -0,0 +1,791 @@ +{ + "type": "new_match", + "params": { + "lives": { + "1": 9, + "2": 9 + }, + "proto_map": { + "external_id": 1, + "segments": [ + [ + [ + 300, + 100 + ], + [ + 900, + 100 + ], + 10 + ], + [ + [ + 300, + 700 + ], + [ + 900, + 700 + ], + 10 + ], + [ + [ + 300.0, + 700.0 + ], + [ + 268.641461019704, + 698.3565686104821 + ], + 10 + ], + [ + [ + 268.641461019704, + 698.3565686104821 + ], + [ + 237.6264927546722, + 693.4442802201418 + ], + 10 + ], + [ + [ + 237.6264927546722, + 693.4442802201418 + ], + [ + 207.2949016875158, + 685.3169548885461 + ], + 10 + ], + [ + [ + 207.2949016875158, + 685.3169548885461 + ], + [ + 177.97900707725995, + 674.0636372927803 + ], + 10 + ], + [ + [ + 177.97900707725995, + 674.0636372927803 + ], + [ + 150.00000000000006, + 659.8076211353316 + ], + 10 + ], + [ + [ + 150.00000000000006, + 659.8076211353316 + ], + [ + 123.66442431225809, + 642.7050983124842 + ], + 10 + ], + [ + [ + 123.66442431225809, + 642.7050983124842 + ], + [ + 99.26081809234262, + 622.9434476432184 + ], + 10 + ], + [ + [ + 99.26081809234262, + 622.9434476432184 + ], + [ + 77.0565523567818, + 600.7391819076574 + ], + 10 + ], + [ + [ + 77.0565523567818, + 600.7391819076574 + ], + [ + 57.29490168751579, + 576.335575687742 + ], + 10 + ], + [ + [ + 57.29490168751579, + 576.335575687742 + ], + [ + 40.192378864668456, + 550.0000000000001 + ], + 10 + ], + [ + [ + 40.192378864668456, + 550.0000000000001 + ], + [ + 25.936362707219757, + 522.0209929227401 + ], + 10 + ], + [ + [ + 25.936362707219757, + 522.0209929227401 + ], + [ + 14.683045111453964, + 492.70509831248427 + ], + 10 + ], + [ + [ + 14.683045111453964, + 492.70509831248427 + ], + [ + 6.55571977985835, + 462.3735072453279 + ], + 10 + ], + [ + [ + 6.55571977985835, + 462.3735072453279 + ], + [ + 1.643431389518014, + 431.35853898029615 + ], + 10 + ], + [ + [ + 1.643431389518014, + 431.35853898029615 + ], + [ + 0.0, + 400.00000000000006 + ], + 10 + ], + [ + [ + 0.0, + 400.00000000000006 + ], + [ + 1.643431389517957, + 368.6414610197041 + ], + 10 + ], + [ + [ + 1.643431389517957, + 368.6414610197041 + ], + [ + 6.555719779858293, + 337.62649275467226 + ], + 10 + ], + [ + [ + 6.555719779858293, + 337.62649275467226 + ], + [ + 14.683045111453907, + 307.2949016875158 + ], + 10 + ], + [ + [ + 14.683045111453907, + 307.2949016875158 + ], + [ + 25.9363627072197, + 277.97900707726006 + ], + 10 + ], + [ + [ + 25.9363627072197, + 277.97900707726006 + ], + [ + 40.19237886466834, + 250.00000000000006 + ], + 10 + ], + [ + [ + 40.19237886466834, + 250.00000000000006 + ], + [ + 57.29490168751576, + 223.6644243122581 + ], + 10 + ], + [ + [ + 57.29490168751576, + 223.6644243122581 + ], + [ + 77.05655235678165, + 199.26081809234262 + ], + 10 + ], + [ + [ + 77.05655235678165, + 199.26081809234262 + ], + [ + 99.26081809234248, + 177.0565523567818 + ], + 10 + ], + [ + [ + 99.26081809234248, + 177.0565523567818 + ], + [ + 123.66442431225803, + 157.2949016875158 + ], + 10 + ], + [ + [ + 123.66442431225803, + 157.2949016875158 + ], + [ + 149.99999999999986, + 140.1923788646685 + ], + 10 + ], + [ + [ + 149.99999999999986, + 140.1923788646685 + ], + [ + 177.97900707725972, + 125.93636270721981 + ], + 10 + ], + [ + [ + 177.97900707725972, + 125.93636270721981 + ], + [ + 207.29490168751573, + 114.68304511145396 + ], + 10 + ], + [ + [ + 207.29490168751573, + 114.68304511145396 + ], + [ + 237.62649275467206, + 106.55571977985835 + ], + 10 + ], + [ + [ + 237.62649275467206, + 106.55571977985835 + ], + [ + 268.64146101970374, + 101.64343138951801 + ], + 10 + ], + [ + [ + 268.64146101970374, + 101.64343138951801 + ], + [ + 299.99999999999994, + 100.0 + ], + 10 + ], + [ + [ + 900.0, + 700.0 + ], + [ + 931.3585389802961, + 698.356568610482 + ], + 10 + ], + [ + [ + 931.3585389802961, + 698.356568610482 + ], + [ + 962.3735072453278, + 693.4442802201416 + ], + 10 + ], + [ + [ + 962.3735072453278, + 693.4442802201416 + ], + [ + 992.7050983124842, + 685.316954888546 + ], + 10 + ], + [ + [ + 992.7050983124842, + 685.316954888546 + ], + [ + 1022.0209929227401, + 674.0636372927802 + ], + 10 + ], + [ + [ + 1022.0209929227401, + 674.0636372927802 + ], + [ + 1050.0, + 659.8076211353316 + ], + 10 + ], + [ + [ + 1050.0, + 659.8076211353316 + ], + [ + 1076.335575687742, + 642.7050983124842 + ], + 10 + ], + [ + [ + 1076.335575687742, + 642.7050983124842 + ], + [ + 1100.7391819076574, + 622.9434476432183 + ], + 10 + ], + [ + [ + 1100.7391819076574, + 622.9434476432183 + ], + [ + 1122.9434476432182, + 600.7391819076574 + ], + 10 + ], + [ + [ + 1122.9434476432182, + 600.7391819076574 + ], + [ + 1142.7050983124843, + 576.335575687742 + ], + 10 + ], + [ + [ + 1142.7050983124843, + 576.335575687742 + ], + [ + 1159.8076211353316, + 550.0 + ], + 10 + ], + [ + [ + 1159.8076211353316, + 550.0 + ], + [ + 1174.0636372927802, + 522.0209929227401 + ], + 10 + ], + [ + [ + 1174.0636372927802, + 522.0209929227401 + ], + [ + 1185.316954888546, + 492.7050983124842 + ], + 10 + ], + [ + [ + 1185.316954888546, + 492.7050983124842 + ], + [ + 1193.4442802201415, + 462.3735072453278 + ], + 10 + ], + [ + [ + 1193.4442802201415, + 462.3735072453278 + ], + [ + 1198.356568610482, + 431.3585389802961 + ], + 10 + ], + [ + [ + 1198.356568610482, + 431.3585389802961 + ], + [ + 1200.0, + 400.0 + ], + 10 + ], + [ + [ + 1200.0, + 400.0 + ], + [ + 1198.356568610482, + 368.64146101970397 + ], + 10 + ], + [ + [ + 1198.356568610482, + 368.64146101970397 + ], + [ + 1193.4442802201418, + 337.62649275467226 + ], + 10 + ], + [ + [ + 1193.4442802201418, + 337.62649275467226 + ], + [ + 1185.316954888546, + 307.2949016875158 + ], + 10 + ], + [ + [ + 1185.316954888546, + 307.2949016875158 + ], + [ + 1174.0636372927802, + 277.97900707725995 + ], + 10 + ], + [ + [ + 1174.0636372927802, + 277.97900707725995 + ], + [ + 1159.8076211353316, + 250.00000000000006 + ], + 10 + ], + [ + [ + 1159.8076211353316, + 250.00000000000006 + ], + [ + 1142.7050983124843, + 223.66442431225806 + ], + 10 + ], + [ + [ + 1142.7050983124843, + 223.66442431225806 + ], + [ + 1122.9434476432184, + 199.2608180923426 + ], + 10 + ], + [ + [ + 1122.9434476432184, + 199.2608180923426 + ], + [ + 1100.7391819076574, + 177.0565523567818 + ], + 10 + ], + [ + [ + 1100.7391819076574, + 177.0565523567818 + ], + [ + 1076.335575687742, + 157.2949016875158 + ], + 10 + ], + [ + [ + 1076.335575687742, + 157.2949016875158 + ], + [ + 1050.0, + 140.19237886466846 + ], + 10 + ], + [ + [ + 1050.0, + 140.19237886466846 + ], + [ + 1022.0209929227401, + 125.93636270721976 + ], + 10 + ], + [ + [ + 1022.0209929227401, + 125.93636270721976 + ], + [ + 992.7050983124842, + 114.68304511145396 + ], + 10 + ], + [ + [ + 992.7050983124842, + 114.68304511145396 + ], + [ + 962.3735072453279, + 106.55571977985835 + ], + 10 + ], + [ + [ + 962.3735072453279, + 106.55571977985835 + ], + [ + 931.3585389802961, + 101.64343138951801 + ], + 10 + ], + [ + [ + 931.3585389802961, + 101.64343138951801 + ], + [ + 900.0, + 100.0 + ], + 10 + ] + ] + }, + "proto_car": { + "car_body_poly": [ + [ + 0, + 6 + ], + [ + 0, + 25 + ], + [ + 33, + 42 + ], + [ + 85, + 42 + ], + [ + 150, + 20 + ], + [ + 150, + 0 + ], + [ + 20, + 0 + ] + ], + "rear_wheel_radius": 12, + "front_wheel_radius": 12, + "button_poly": [ + [ + 40, + 42 + ], + [ + 40.0, + 43.0 + ], + [ + 78.0, + 43.0 + ], + [ + 78.0, + 42.0 + ] + ], + "external_id": 1, + "car_body_mass": 200, + "car_body_friction": 0.9, + "car_body_elasticity": 0.5, + "max_speed": 70, + "max_angular_speed": 2, + "drive": 2, + "rear_wheel_mass": 50, + "rear_wheel_position": [ + 29, + -5 + ], + "rear_wheel_friction": 1, + "rear_wheel_elasticity": 0.8, + "rear_wheel_joint": [ + 150, + 0 + ], + "rear_wheel_damp_position": [ + 29, + 20 + ], + "rear_wheel_damp_length": 25, + "rear_wheel_damp_stiffness": 50000.0, + "rear_wheel_damp_damping": 3000.0, + "front_wheel_mass": 5, + "front_wheel_position": [ + 122, + -5 + ], + "front_wheel_friction": 1, + "front_wheel_elasticity": 0.8, + "front_wheel_joint": [ + 0, + 6 + ], + "front_wheel_damp_position": [ + 122, + 20 + ], + "front_wheel_damp_length": 25, + "front_wheel_damp_stiffness": 60000.0, + "front_wheel_damp_damping": 900.0 + } + } +}, \ No newline at end of file From 19ea0fe6089eddb9d19b2ecbb5ebee1b93f742e5 Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Thu, 6 Sep 2018 01:41:19 +0300 Subject: [PATCH 23/96] rules fix --- madcars/RULES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/madcars/RULES.md b/madcars/RULES.md index c718ab2..ee65a0a 100644 --- a/madcars/RULES.md +++ b/madcars/RULES.md @@ -287,7 +287,7 @@ while True: * `rear_wheel_position` - положение заднего колеса в координатах относительно кузова машины * `rear_wheel_friction` - трение заднего колеса * `rear_wheel_elasticity` - эластичность заднего колеса - * `rear_wheel_joint` - положение жесткого соединения заднего колеса + * `rear_wheel_groove_offset` - смещение крепления заднего колеса относительно кузова * `rear_wheel_damp_position` - положение пружины заднего колеса * `rear_wheel_damp_length` - длина пружины заднего колеса * `rear_wheel_damp_stiffness` - жесткость пружины заднего колеса @@ -298,7 +298,7 @@ while True: * `front_wheel_position` - положение переднего колеса в координатах относительно кузова машины * `front_wheel_friction` - трение переднего колеса * `front_wheel_elasticity` - эластичность переднего колеса - * `front_wheel_joint` - положение жесткого соединения переднего колеса + * `front_wheel_groove_offset` - смещение крепления переднего колеса относительно кузова * `front_wheel_damp_position` - положение пружины переднего колеса * `front_wheel_damp_length` - длина пружины переднего колеса * `front_wheel_damp_stiffness` - жесткость пружины переднего колеса From d9350887ffd058f2622baf7bb61d4daa2ffdfe69 Mon Sep 17 00:00:00 2001 From: adler3d Date: Thu, 6 Sep 2018 02:10:19 +0300 Subject: [PATCH 24/96] grove -> groove --- madcars/Runners/mechanic/game_objects/base_car.py | 8 ++++---- madcars/Runners/mechanic/game_objects/cars.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/madcars/Runners/mechanic/game_objects/base_car.py b/madcars/Runners/mechanic/game_objects/base_car.py index 394ec23..c5bd5f4 100644 --- a/madcars/Runners/mechanic/game_objects/base_car.py +++ b/madcars/Runners/mechanic/game_objects/base_car.py @@ -86,7 +86,7 @@ def create_wheel(self, wheel_side): wheel_position = getattr(self, wheel_side + '_wheel_position') wheel_friction = getattr(self, wheel_side + '_wheel_friction') wheel_elasticity = getattr(self, wheel_side + '_wheel_elasticity') - wheel_grove_offset = getattr(self, wheel_side + '_wheel_groove_offset') + wheel_groove_offset = getattr(self, wheel_side + '_wheel_groove_offset') wheel_damp_position = getattr(self, wheel_side + '_wheel_damp_position') wheel_damp_length = getattr(self, wheel_side + '_wheel_damp_length') wheel_damp_stiffness = getattr(self, wheel_side + '_wheel_damp_stiffness') @@ -102,12 +102,12 @@ def create_wheel(self, wheel_side): wheel_shape.elasticity = wheel_elasticity wheel_objects.append(wheel_shape) - wheel_grove = pymunk.GrooveJoint(self.car_body, wheel_body, - (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1] - wheel_grove_offset), + wheel_groove = pymunk.GrooveJoint(self.car_body, wheel_body, + (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1] - wheel_groove_offset), (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1] - wheel_damp_length * 1.5), (0, 0)) - wheel_objects.append(wheel_grove) + wheel_objects.append(wheel_groove) wheel_damp = pymunk.DampedSpring(wheel_body, self.car_body, anchor_a=(0, 0), anchor_b=(wheel_damp_position[0] * self.x_modification, wheel_damp_position[1]), diff --git a/madcars/Runners/mechanic/game_objects/cars.py b/madcars/Runners/mechanic/game_objects/cars.py index 50f1a40..90e5f15 100644 --- a/madcars/Runners/mechanic/game_objects/cars.py +++ b/madcars/Runners/mechanic/game_objects/cars.py @@ -104,7 +104,7 @@ def create_wheel(self, wheel_side): wheel_position = getattr(self, wheel_side + '_wheel_position') wheel_friction = getattr(self, wheel_side + '_wheel_friction') wheel_elasticity = getattr(self, wheel_side + '_wheel_elasticity') - wheel_grove_offset = getattr(self, wheel_side + '_wheel_groove_offset') + wheel_groove_offset = getattr(self, wheel_side + '_wheel_groove_offset') wheel_damp_position = getattr(self, wheel_side + '_wheel_damp_position') wheel_damp_length = getattr(self, wheel_side + '_wheel_damp_length') wheel_damp_stiffness = getattr(self, wheel_side + '_wheel_damp_stiffness') @@ -120,12 +120,12 @@ def create_wheel(self, wheel_side): wheel_shape.elasticity = wheel_elasticity wheel_objects.append(wheel_shape) - wheel_grove = pymunk.GrooveJoint(self.car_body, wheel_body, - (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1] - wheel_grove_offset), + wheel_groove = pymunk.GrooveJoint(self.car_body, wheel_body, + (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1] - wheel_groove_offset), (wheel_damp_position[0] * self.x_modification, wheel_damp_position[1] - wheel_damp_length * 1.5), (0, 0)) - wheel_objects.append(wheel_grove) + wheel_objects.append(wheel_groove) wheel_damp = pymunk.DampedSpring(wheel_body, self.car_body, anchor_a=(0, 0), anchor_b=(wheel_damp_position[0] * self.x_modification, wheel_damp_position[1]), From 166d9dbe2528cc5e2483d3cdf86d026c7972f312 Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Thu, 6 Sep 2018 08:20:51 +0300 Subject: [PATCH 25/96] Implemented custom __build__.sh for c++ solutions --- madcars/dockers/cpp11/Dockerfile | 3 ++- madcars/dockers/cpp17/Dockerfile | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/madcars/dockers/cpp11/Dockerfile b/madcars/dockers/cpp11/Dockerfile index ecded35..5d3695c 100644 --- a/madcars/dockers/cpp11/Dockerfile +++ b/madcars/dockers/cpp11/Dockerfile @@ -3,7 +3,8 @@ MAINTAINER Boris Kolganov ENV COMPILED_FILE_PATH=/opt/client/a.out ENV SOLUTION_CODE_ENTRYPOINT=main.cpp -ENV COMPILATION_COMMAND='g++ -m64 -pipe -O2 -std=c++11 -pthread -w -o $COMPILED_FILE_PATH $SOLUTION_CODE_PATH/main.cpp 2>&1 > /dev/null' +ENV DEFALUT_COMPILATION_COMMAND='g++ -m64 -pipe -O2 -std=c++11 -pthread -w -o $COMPILED_FILE_PATH $SOLUTION_CODE_PATH/main.cpp 2>&1 > /dev/null' +ENV COMPILATION_COMMAND='if [ -f $SOLUTION_CODE_PATH/__build__.sh ]; then cd $SOLUTION_CODE_PATH; . $SOLUTION_CODE_PATH/__build__.sh; else $DEFALUT_COMPILATION_COMMAND; fi 2>&1 > /dev/null' ENV RUN_COMMAND='/lib64/ld-linux-x86-64.so.2 $MOUNT_POINT' COPY ./nlohmann ./nlohmann diff --git a/madcars/dockers/cpp17/Dockerfile b/madcars/dockers/cpp17/Dockerfile index f98ba88..7e63fcc 100644 --- a/madcars/dockers/cpp17/Dockerfile +++ b/madcars/dockers/cpp17/Dockerfile @@ -6,7 +6,7 @@ RUN \ apt-get install -y software-properties-common && \ add-apt-repository ppa:ubuntu-toolchain-r/test -y && \ apt-get update -y && \ - apt-get install -y g++-7 make + apt-get install -y g++-7 make cmake COPY Makefile ./ COPY ./nlohmann ./nlohmann @@ -14,5 +14,5 @@ COPY ./nlohmann ./nlohmann ENV SOLUTION_CODE_ENTRYPOINT=main.cpp ENV COMPILED_FILE_PATH=/opt/client/a.out ENV SOLUTION_CODE_PATH=/opt/client/solution/ -ENV COMPILATION_COMMAND='make 2>&1 > /dev/null' +ENV COMPILATION_COMMAND='if [ -f $SOLUTION_CODE_PATH/__build__.sh ]; then cd $SOLUTION_CODE_PATH; . $SOLUTION_CODE_PATH/__build__.sh; else make; fi 2>&1 > /dev/null' ENV RUN_COMMAND='/lib64/ld-linux-x86-64.so.2 $MOUNT_POINT' From 6a87bce614e612662684e8f177b5932ec6839743 Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Fri, 7 Sep 2018 00:46:18 +0300 Subject: [PATCH 26/96] add rest time to match --- madcars/Runners/mechanic/constants.py | 1 + madcars/Runners/mechanic/game.py | 2 +- madcars/Runners/mechanic/match.py | 36 +++++++++++++++++++-------- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/madcars/Runners/mechanic/constants.py b/madcars/Runners/mechanic/constants.py index f5bb213..fade983 100644 --- a/madcars/Runners/mechanic/constants.py +++ b/madcars/Runners/mechanic/constants.py @@ -16,5 +16,6 @@ def toint(value, default=None): MAX_TICK_COUNT = toint(os.getenv('MAX_TICK_COUNT'), 20000) SEED = toint(os.getenv('SEED'), random.randint(0, 2**128)) MATCHES_COUNT = toint(os.getenv('MATCHES_COUNT'), 9) +REST_TICKS = toint(os.getenv('REST_TICKS'), 90) random.seed(SEED) diff --git a/madcars/Runners/mechanic/game.py b/madcars/Runners/mechanic/game.py index 52b704f..e0fdc3a 100644 --- a/madcars/Runners/mechanic/game.py +++ b/madcars/Runners/mechanic/game.py @@ -107,7 +107,7 @@ def tick(self): yield from self.current_match.tick(self.tick_num) self.space.step(0.016) - if self.current_match.smbd_die(): + if self.current_match.is_match_ended(): self.game_log.extend(self.current_match.end_match()) if not all([p.is_alive() for p in self.all_players]): diff --git a/madcars/Runners/mechanic/match.py b/madcars/Runners/mechanic/match.py index ff7d329..bf08d0a 100644 --- a/madcars/Runners/mechanic/match.py +++ b/madcars/Runners/mechanic/match.py @@ -4,7 +4,7 @@ import os from pymunk import Vec2d -from mechanic.constants import TICKS_TO_DEADLINE +from mechanic.constants import TICKS_TO_DEADLINE, REST_TICKS from mechanic.game_objects.base_car import Car from mechanic.game_objects.deadline import DeadLine @@ -22,6 +22,9 @@ def __init__(self, map, car, players, space): self.map_objects.append(self.deadline.get_object_for_space()) self.dead_players = set() + self.is_rest = False + self.rest_counter = 0 + self.match_log = [] for index, player in enumerate(self.players): @@ -40,7 +43,8 @@ def __init__(self, map, car, players, space): @asyncio.coroutine def apply_turn_wrapper(self, player, game_tick): - yield from player.apply_turn(game_tick) + if not self.is_rest: + yield from player.apply_turn(game_tick) @asyncio.coroutine def tick(self, game_tick): @@ -56,6 +60,13 @@ def tick(self, game_tick): else: self.ticks_to_deadline -= 1 + if not self.is_rest and self.smbd_die(): + self.rest_counter = REST_TICKS + self.is_rest = True + + if self.rest_counter > 0: + self.rest_counter -= 1 + def get_objects_for_space(self): return self.map_objects + self.cars_objects @@ -116,21 +127,26 @@ def send_tick(self, game_tick): } }) - for p in self.players: - my_car, enemy_car = self.get_players_car(p) - p.send_message('tick', { - 'my_car': my_car, - 'enemy_car': enemy_car, - 'deadline_position': self.deadline.get_position() - }) + if not self.is_rest: + for p in self.players: + my_car, enemy_car = self.get_players_car(p) + p.send_message('tick', { + 'my_car': my_car, + 'enemy_car': enemy_car, + 'deadline_position': self.deadline.get_position() + }) def lose_callback(self, player, arbiter, space, _): - self.dead_players.add(player) + if not self.is_rest: + self.dead_players.add(player) return False def smbd_die(self): return bool(self.dead_players) + def is_match_ended(self): + return self.rest_counter == 0 and bool(self.dead_players) and self.is_rest + def end_match(self): for p in self.dead_players: p.die() From 28a9517237f007befdf023f63214750d1a911048 Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Fri, 7 Sep 2018 08:37:23 +0300 Subject: [PATCH 27/96] Fixed default build in cpp11 --- madcars/dockers/cpp11/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/madcars/dockers/cpp11/Dockerfile b/madcars/dockers/cpp11/Dockerfile index 5d3695c..3715f3f 100644 --- a/madcars/dockers/cpp11/Dockerfile +++ b/madcars/dockers/cpp11/Dockerfile @@ -4,7 +4,7 @@ MAINTAINER Boris Kolganov ENV COMPILED_FILE_PATH=/opt/client/a.out ENV SOLUTION_CODE_ENTRYPOINT=main.cpp ENV DEFALUT_COMPILATION_COMMAND='g++ -m64 -pipe -O2 -std=c++11 -pthread -w -o $COMPILED_FILE_PATH $SOLUTION_CODE_PATH/main.cpp 2>&1 > /dev/null' -ENV COMPILATION_COMMAND='if [ -f $SOLUTION_CODE_PATH/__build__.sh ]; then cd $SOLUTION_CODE_PATH; . $SOLUTION_CODE_PATH/__build__.sh; else $DEFALUT_COMPILATION_COMMAND; fi 2>&1 > /dev/null' +ENV COMPILATION_COMMAND='if [ -f $SOLUTION_CODE_PATH/__build__.sh ]; then cd $SOLUTION_CODE_PATH; . $SOLUTION_CODE_PATH/__build__.sh; else eval "$DEFALUT_COMPILATION_COMMAND"; fi 2>&1 > /dev/null' ENV RUN_COMMAND='/lib64/ld-linux-x86-64.so.2 $MOUNT_POINT' COPY ./nlohmann ./nlohmann From 2fae68486dc0fd0a8ae16fe01f2586e9481fa6dd Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Sat, 8 Sep 2018 10:13:17 +0300 Subject: [PATCH 28/96] Prevent sending the first rest tick to the strategy --- madcars/Runners/mechanic/match.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/madcars/Runners/mechanic/match.py b/madcars/Runners/mechanic/match.py index bf08d0a..402533c 100644 --- a/madcars/Runners/mechanic/match.py +++ b/madcars/Runners/mechanic/match.py @@ -48,6 +48,13 @@ def apply_turn_wrapper(self, player, game_tick): @asyncio.coroutine def tick(self, game_tick): + if not self.is_rest and self.smbd_die(): + self.rest_counter = REST_TICKS + self.is_rest = True + + if self.rest_counter > 0: + self.rest_counter -= 1 + self.send_tick(game_tick) futures = [] for p in self.players: @@ -60,13 +67,6 @@ def tick(self, game_tick): else: self.ticks_to_deadline -= 1 - if not self.is_rest and self.smbd_die(): - self.rest_counter = REST_TICKS - self.is_rest = True - - if self.rest_counter > 0: - self.rest_counter -= 1 - def get_objects_for_space(self): return self.map_objects + self.cars_objects From 856df70656fce376cc6f33d5e0762cf6c7dcefd7 Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Sat, 8 Sep 2018 14:19:33 +0300 Subject: [PATCH 29/96] add random command if command not in stop/left/right --- madcars/Runners/mechanic/player.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/madcars/Runners/mechanic/player.py b/madcars/Runners/mechanic/player.py index a833925..09a4d73 100644 --- a/madcars/Runners/mechanic/player.py +++ b/madcars/Runners/mechanic/player.py @@ -1,4 +1,5 @@ import asyncio +import random class Player(object): @@ -41,6 +42,10 @@ def apply_turn(self, tick): self.car.go_left() elif turn == 'right': self.car.go_right() + else: + action = random.choice((self.car.stop, self.car.go_left, self.car.go_right)) + action() + except Exception as e: args = e.args if len(args) > 0: From 9f5f3840710c70695ed3478251b4d0f636258a27 Mon Sep 17 00:00:00 2001 From: Tony Kozlovsky Date: Thu, 6 Sep 2018 12:34:09 +0300 Subject: [PATCH 30/96] add cpp17chipmunk docker --- madcars/dockers/cpp17chipmunk/Dockerfile | 19 + madcars/dockers/cpp17chipmunk/LibMakefile | 14 + madcars/dockers/cpp17chipmunk/README.md | 26 + .../dockers/cpp17chipmunk/SolutionMakefile | 8 + .../cpp17chipmunk/nlohmann/LICENSE.MIT | 21 + .../dockers/cpp17chipmunk/nlohmann/json.hpp | 17190 ++++++++++++++++ 6 files changed, 17278 insertions(+) create mode 100644 madcars/dockers/cpp17chipmunk/Dockerfile create mode 100644 madcars/dockers/cpp17chipmunk/LibMakefile create mode 100644 madcars/dockers/cpp17chipmunk/README.md create mode 100644 madcars/dockers/cpp17chipmunk/SolutionMakefile create mode 100644 madcars/dockers/cpp17chipmunk/nlohmann/LICENSE.MIT create mode 100644 madcars/dockers/cpp17chipmunk/nlohmann/json.hpp diff --git a/madcars/dockers/cpp17chipmunk/Dockerfile b/madcars/dockers/cpp17chipmunk/Dockerfile new file mode 100644 index 0000000..35c97a0 --- /dev/null +++ b/madcars/dockers/cpp17chipmunk/Dockerfile @@ -0,0 +1,19 @@ +FROM ubuntu:16.04 +MAINTAINER Tony Kozlovsky + +RUN \ + apt-get update -y && \ + apt-get install -y software-properties-common && \ + add-apt-repository ppa:ubuntu-toolchain-r/test -y && \ + apt-get update -y && \ + apt-get install -y g++-7 build-essential make + +ENV COMPILED_FILE_PATH=/opt/client/a.out +ENV SOLUTION_CODE_PATH=/opt/client/solution +ENV SOLUTION_LIBRARY_PATH=$SOLUTION_CODE_PATH/chipmunk_src +ENV COMPILATION_COMMAND='make -C $SOLUTION_LIBRARY_PATH -f /opt/client/LibMakefile && make -C $SOLUTION_CODE_PATH -f /opt/client/SolutionMakefile' +ENV RUN_COMMAND='/lib64/ld-linux-x86-64.so.2 $MOUNT_POINT' + +COPY ./nlohmann ./nlohmann +COPY ./LibMakefile ./ +COPY ./SolutionMakefile ./ diff --git a/madcars/dockers/cpp17chipmunk/LibMakefile b/madcars/dockers/cpp17chipmunk/LibMakefile new file mode 100644 index 0000000..ee87f03 --- /dev/null +++ b/madcars/dockers/cpp17chipmunk/LibMakefile @@ -0,0 +1,14 @@ +CC = cc +FLAGS = -std=gnu99 -ffast-math -DCHIPMUNK_FFI -g -DCP_USE_CGPOINTS=0 -DNDEBUG -fPIC -O3 -m64 + +SOURCES = $(shell echo ./src/*.c) +INCLUDE = ./include + +OBJECTS = $(SOURCES:.c=.o) + +all: $(OBJECTS) + +$(OBJECTS): %.o: %.c + $(CC) $(FLAGS) -I$(INCLUDE) -c $< -o $@ + + diff --git a/madcars/dockers/cpp17chipmunk/README.md b/madcars/dockers/cpp17chipmunk/README.md new file mode 100644 index 0000000..e4689fc --- /dev/null +++ b/madcars/dockers/cpp17chipmunk/README.md @@ -0,0 +1,26 @@ +### Описание: +Данный докер позволяет собирать решение **написанное на C++**, в котором используется физический +движок chipmunk, **написанный на С** +(возможно модифицированный), который будет собран **тем же способом**, которым он был собран +обёрткой +Pymunk + +### Структура решения: +Предполагается, что С код будет написан на основе иходников физического движка chipmunk, +предоставленных здесь: +https://github.com/viblo/pymunk + +В **отправляемом архиве** должно быть **две** папки: +1. **chipmunk_src** (структура этой папки должна соответствовать https://github.com/viblo/pymunk/tree/master/chipmunk_src) +2. **solution** (ограничений на структуру этой папки нет) + +**Первая** будет скомпилирована компилятором для C\ +**Вторая** - компилятором для C++ + +### Описание компиляторов: +* Компилятор для C: `cc` +* Флаги компиляции для C: `-std=gnu99 -ffast-math -DCHIPMUNK_FFI -g -DCP_USE_CGPOINTS=0 -DNDEBUG +-fPIC -O3 -m64` + +* Компилятор для C++: `g++-7` +* Флаги компиляции для C++: `-std=c++17 -O3 -m64 -pipe -w -pthread` \ No newline at end of file diff --git a/madcars/dockers/cpp17chipmunk/SolutionMakefile b/madcars/dockers/cpp17chipmunk/SolutionMakefile new file mode 100644 index 0000000..6fbd24b --- /dev/null +++ b/madcars/dockers/cpp17chipmunk/SolutionMakefile @@ -0,0 +1,8 @@ +CXXFLAGS=-std=c++17 -O3 -m64 -pipe -w -pthread +CXX=g++-7 + +SRCS = $(shell find ./ -type f -name '*.cpp') +OBJS = $(shell find ./ -type f -name '*.o') + +all: ${SRCS} ${OBJS} + ${CXX} ${CXXFLAGS} -o ${COMPILED_FILE_PATH} ${SRCS} ${OBJS} -I${SOLUTION_LIBRARY_PATH}/include diff --git a/madcars/dockers/cpp17chipmunk/nlohmann/LICENSE.MIT b/madcars/dockers/cpp17chipmunk/nlohmann/LICENSE.MIT new file mode 100644 index 0000000..8b0f700 --- /dev/null +++ b/madcars/dockers/cpp17chipmunk/nlohmann/LICENSE.MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-2018 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. diff --git a/madcars/dockers/cpp17chipmunk/nlohmann/json.hpp b/madcars/dockers/cpp17chipmunk/nlohmann/json.hpp new file mode 100644 index 0000000..3dcb834 --- /dev/null +++ b/madcars/dockers/cpp17chipmunk/nlohmann/json.hpp @@ -0,0 +1,17190 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 3.1.1 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +Copyright (c) 2013-2018 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. +*/ + +#ifndef NLOHMANN_JSON_HPP +#define NLOHMANN_JSON_HPP + +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 1 +#define NLOHMANN_JSON_VERSION_PATCH 1 + +#include // all_of, find, for_each +#include // assert +#include // and, not, or +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#include // istream, ostream +#include // iterator_traits, random_access_iterator_tag +#include // accumulate +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap + +// #include +#ifndef NLOHMANN_JSON_FWD_HPP +#define NLOHMANN_JSON_FWD_HPP + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string +#include // vector + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} + +#endif + +// #include + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// exclude unsupported compilers +#if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow for portable deprecation warnings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define JSON_DEPRECATED __declspec(deprecated) +#else + #define JSON_DEPRECATED +#endif + +// allow to disable exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) +#else + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER +#endif + +// manual branch prediction +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) + #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else + #define JSON_LIKELY(x) x + #define JSON_UNLIKELY(x) x +#endif + +// C++ language standard detection +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +/*! +@brief Helper to determine whether there's a key_type for T. + +This helper is used to tell associative containers apart from other containers +such as sequence containers. For instance, `std::map` passes the test as it +contains a `mapped_type`, whereas `std::vector` fails the test. + +@sa http://stackoverflow.com/a/7728728/266378 +@since version 1.0.0, overworked in version 2.0.6 +*/ +#define NLOHMANN_JSON_HAS_HELPER(type) \ + template struct has_##type { \ + private: \ + template \ + static int detect(U &&); \ + static void detect(...); \ + public: \ + static constexpr bool value = \ + std::is_integral()))>::value; \ + } + +// #include + + +#include // not +#include // size_t +#include // numeric_limits +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // declval + +// #include + +// #include + + +namespace nlohmann +{ +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ +///////////// +// helpers // +///////////// + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +/* +Implementation of two C++17 constructs: conjunction, negation. This is needed +to avoid evaluating all the traits in a condition + +For example: not std::is_same::value and has_value_type::value +will not compile when T = void (on MSVC at least). Whereas +conjunction>, has_value_type>::value will +stop evaluating if negation<...>::value == false + +Please note that those constructs must be used with caution, since symbols can +become very long quickly (which can slow down compilation and cause MSVC +internal compiler errors). Only use it when you have to (see example ahead). +*/ +template struct conjunction : std::true_type {}; +template struct conjunction : B1 {}; +template +struct conjunction : std::conditional, B1>::type {}; + +template struct negation : std::integral_constant {}; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +//////////////////////// +// has_/is_ functions // +//////////////////////// + +// source: https://stackoverflow.com/a/37193089/4116453 + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +NLOHMANN_JSON_HAS_HELPER(mapped_type); +NLOHMANN_JSON_HAS_HELPER(key_type); +NLOHMANN_JSON_HAS_HELPER(value_type); +NLOHMANN_JSON_HAS_HELPER(iterator); + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl +{ + static constexpr auto value = + std::is_constructible::value and + std::is_constructible::value; +}; + +template +struct is_compatible_object_type +{ + static auto constexpr value = is_compatible_object_type_impl < + conjunction>, + has_mapped_type, + has_key_type>::value, + typename BasicJsonType::object_t, CompatibleObjectType >::value; +}; + +template +struct is_basic_json_nested_type +{ + static auto constexpr value = std::is_same::value or + std::is_same::value or + std::is_same::value or + std::is_same::value; +}; + +template +struct is_compatible_array_type +{ + static auto constexpr value = + conjunction>, + negation>, + negation>, + negation>, + has_value_type, + has_iterator>::value; +}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + std::is_constructible::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type +{ + static constexpr auto value = + is_compatible_integer_type_impl < + std::is_integral::value and + not std::is_same::value, + RealIntegerType, CompatibleNumberIntegerType > ::value; +}; + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json +{ + private: + // also check the return type of from_json + template::from_json( + std::declval(), std::declval()))>::value>> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json +{ + private: + template < + typename U, + typename = enable_if_t::from_json(std::declval()))>::value >> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +template +struct has_to_json +{ + private: + template::to_json( + std::declval(), std::declval()))> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +template +struct is_compatible_complete_type +{ + static constexpr bool value = + not std::is_base_of::value and + not std::is_same::value and + not is_basic_json_nested_type::value and + has_to_json::value; +}; + +template +struct is_compatible_type + : conjunction, + is_compatible_complete_type> +{ +}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} +} + +// #include + + +#include // exception +#include // runtime_error +#include // to_string + +namespace nlohmann +{ +namespace detail +{ +//////////////// +// exceptions // +//////////////// + +/*! +@brief general exception of the @ref basic_json class + +This class is an extension of `std::exception` objects with a member @a id for +exception ids. It is used as the base class for all exceptions thrown by the +@ref basic_json class. This class can hence be used as "wildcard" to catch +exceptions. + +Subclasses: +- @ref parse_error for exceptions indicating a parse error +- @ref invalid_iterator for exceptions indicating errors with iterators +- @ref type_error for exceptions indicating executing a member function with + a wrong type +- @ref out_of_range for exceptions indicating access out of the defined range +- @ref other_error for exceptions indicating other library errors + +@internal +@note To have nothrow-copy-constructible exceptions, we internally use + `std::runtime_error` which can cope with arbitrary-length error messages. + Intermediate strings are built with static functions and then passed to + the actual constructor. +@endinternal + +@liveexample{The following code shows how arbitrary library exceptions can be +caught.,exception} + +@since version 3.0.0 +*/ +class exception : public std::exception +{ + public: + /// returns the explanatory string + const char* what() const noexcept override + { + return m.what(); + } + + /// the id of the exception + const int id; + + protected: + exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} + + static std::string name(const std::string& ename, int id_) + { + return "[json.exception." + ename + "." + std::to_string(id_) + "] "; + } + + private: + /// an exception object as storage for error messages + std::runtime_error m; +}; + +/*! +@brief exception indicating a parse error + +This exception is thrown by the library when a parse error occurs. Parse errors +can occur during the deserialization of JSON text, CBOR, MessagePack, as well +as when using JSON Patch. + +Member @a byte holds the byte index of the last read character in the input +file. + +Exceptions have ids 1xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. +json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. +json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. +json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. +json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. +json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. +json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. +json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. +json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. +json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. +json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. + +@note For an input with n bytes, 1 is the index of the first character and n+1 + is the index of the terminating null byte or the end of file. This also + holds true when reading a byte vector (CBOR or MessagePack). + +@liveexample{The following code shows how a `parse_error` exception can be +caught.,parse_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class parse_error : public exception +{ + public: + /*! + @brief create a parse error exception + @param[in] id_ the id of the exception + @param[in] byte_ the byte index where the error occurred (or 0 if the + position cannot be determined) + @param[in] what_arg the explanatory string + @return parse_error object + */ + static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + (byte_ != 0 ? (" at " + std::to_string(byte_)) : "") + + ": " + what_arg; + return parse_error(id_, byte_, w.c_str()); + } + + /*! + @brief byte index of the parse error + + The byte index of the last read character in the input file. + + @note For an input with n bytes, 1 is the index of the first character and + n+1 is the index of the terminating null byte or the end of file. + This also holds true when reading a byte vector (CBOR or MessagePack). + */ + const std::size_t byte; + + private: + parse_error(int id_, std::size_t byte_, const char* what_arg) + : exception(id_, what_arg), byte(byte_) {} +}; + +/*! +@brief exception indicating errors with iterators + +This exception is thrown if iterators passed to a library function do not match +the expected semantics. + +Exceptions have ids 2xx. + +name / id | example message | description +----------------------------------- | --------------- | ------------------------- +json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. +json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. +json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. +json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. +json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. +json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. +json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. +json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. +json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. +json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). + +@liveexample{The following code shows how an `invalid_iterator` exception can be +caught.,invalid_iterator} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class invalid_iterator : public exception +{ + public: + static invalid_iterator create(int id_, const std::string& what_arg) + { + std::string w = exception::name("invalid_iterator", id_) + what_arg; + return invalid_iterator(id_, w.c_str()); + } + + private: + invalid_iterator(int id_, const char* what_arg) + : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating executing a member function with a wrong type + +This exception is thrown in case of a type error; that is, a library function is +executed on a JSON value whose type does not match the expected semantics. + +Exceptions have ids 3xx. + +name / id | example message | description +----------------------------- | --------------- | ------------------------- +json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. +json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. +json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t&. +json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. +json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. +json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. +json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. +json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. +json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. +json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. +json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. +json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. +json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. +json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. +json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. +json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | + +@liveexample{The following code shows how a `type_error` exception can be +caught.,type_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class type_error : public exception +{ + public: + static type_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("type_error", id_) + what_arg; + return type_error(id_, w.c_str()); + } + + private: + type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating access out of the defined range + +This exception is thrown in case a library function is called on an input +parameter that exceeds the expected range, for instance in case of array +indices or nonexisting object keys. + +Exceptions have ids 4xx. + +name / id | example message | description +------------------------------- | --------------- | ------------------------- +json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. +json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. +json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. +json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. +json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. +json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. +json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON only supports integers numbers up to 9223372036854775807. | +json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | + +@liveexample{The following code shows how an `out_of_range` exception can be +caught.,out_of_range} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class out_of_range : public exception +{ + public: + static out_of_range create(int id_, const std::string& what_arg) + { + std::string w = exception::name("out_of_range", id_) + what_arg; + return out_of_range(id_, w.c_str()); + } + + private: + out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating other library errors + +This exception is thrown in case of errors that cannot be classified with the +other exception types. + +Exceptions have ids 5xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range + +@liveexample{The following code shows how an `other_error` exception can be +caught.,other_error} + +@since version 3.0.0 +*/ +class other_error : public exception +{ + public: + static other_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("other_error", id_) + what_arg; + return other_error(id_, w.c_str()); + } + + private: + other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; +} +} + +// #include + + +#include // array +#include // and +#include // size_t +#include // uint8_t + +namespace nlohmann +{ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + discarded ///< discarded by the the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string +- furthermore, each type is not smaller than itself +- discarded values are not comparable + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); + return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index]; +} +} +} + +// #include + + +#include // transform +#include // array +#include // and, not +#include // forward_list +#include // inserter, front_inserter, end +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // pair, declval +#include // valarray + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// overloads for basic_json template parameters +template::value and + not std::is_same::value, + int> = 0> +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (JSON_UNLIKELY(not j.is_boolean())) + { + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); + } + b = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (JSON_UNLIKELY(not j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); + } + s = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, EnumType& e) +{ + typename std::underlying_type::type val; + get_arithmetic_value(j, val); + e = static_cast(val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + arr = *j.template get_ptr(); +} + +// forward_list doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::forward_list& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + std::transform(j.rbegin(), j.rend(), + std::front_inserter(l), [](const BasicJsonType & i) + { + return i.template get(); + }); +} + +// valarray doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::valarray& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + l.resize(j.size()); + std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l)); +} + +template +void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0> /*unused*/) +{ + using std::end; + + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) +-> decltype( + arr.reserve(std::declval()), + void()) +{ + using std::end; + + arr.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +void from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get(); + } +} + +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < + is_compatible_array_type::value and + not std::is_same::value and + std::is_constructible < + BasicJsonType, typename CompatibleArrayType::value_type >::value, + int > = 0 > +void from_json(const BasicJsonType& j, CompatibleArrayType& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + + std::string(j.type_name()))); + } + + from_json_array_impl(j, arr, priority_tag<2> {}); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +{ + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); + } + + auto inner_object = j.template get_ptr(); + using value_type = typename CompatibleObjectType::value_type; + std::transform( + inner_object->begin(), inner_object->end(), + std::inserter(obj, obj.begin()), + [](typename BasicJsonType::object_t::value_type const & p) + { + return value_type(p.first, p.second.template get()); + }); +} + +// overload for arithmetic types, not chosen for basic_json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for boolean_t etc. in case of a custom BooleanType which is not +// an arithmetic type? +template::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value, + int> = 0> +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::boolean: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, std::pair& p) +{ + p = {j.at(0).template get(), j.at(1).template get()}; +} + +template +void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence) +{ + t = std::make_tuple(j.at(Idx).template get::type>()...); +} + +template +void from_json(const BasicJsonType& j, std::tuple& t) +{ + from_json_tuple_impl(j, t, index_sequence_for {}); +} + +struct from_json_fn +{ + private: + template + auto call(const BasicJsonType& j, T& val, priority_tag<1> /*unused*/) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } + + template + void call(const BasicJsonType& /*unused*/, T& /*unused*/, priority_tag<0> /*unused*/) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find from_json() method in T's namespace"); +#ifdef _MSC_VER + // MSVC does not show a stacktrace for the above assert + using decayed = uncvref_t; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif + } + + public: + template + void operator()(const BasicJsonType& j, T& val) const + noexcept(noexcept(std::declval().call(j, val, priority_tag<1> {}))) + { + return call(j, val, priority_tag<1> {}); + } +}; +} + +/// namespace to hold default `from_json` function +/// to see why this is required: +/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html +namespace +{ +constexpr const auto& from_json = detail::static_const::value; +} +} + +// #include + + +#include // or, and, not +#include // begin, end +#include // tuple, get +#include // is_same, is_constructible, is_floating_point, is_enum, underlying_type +#include // move, forward, declval, pair +#include // valarray +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +////////////////// +// constructors // +////////////////// + +template struct external_constructor; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept + { + j.m_type = value_t::boolean; + j.m_value = b; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) + { + j.m_type = value_t::string; + j.m_value = s; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) + { + j.m_type = value_t::string; + j.m_value = std::move(s); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept + { + j.m_type = value_t::number_float; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept + { + j.m_type = value_t::number_unsigned; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept + { + j.m_type = value_t::number_integer; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) + { + j.m_type = value_t::array; + j.m_value = std::move(arr); + j.assert_invariant(); + } + + template::value, + int> = 0> + static void construct(BasicJsonType& j, const CompatibleArrayType& arr) + { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = j.template create(begin(arr), end(arr)); + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, const std::vector& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->reserve(arr.size()); + for (const bool x : arr) + { + j.m_value.array->push_back(x); + } + j.assert_invariant(); + } + + template::value, int> = 0> + static void construct(BasicJsonType& j, const std::valarray& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->resize(arr.size()); + std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) + { + j.m_type = value_t::object; + j.m_value = obj; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) + { + j.m_type = value_t::object; + j.m_value = std::move(obj); + j.assert_invariant(); + } + + template::value, int> = 0> + static void construct(BasicJsonType& j, const CompatibleObjectType& obj) + { + using std::begin; + using std::end; + + j.m_type = value_t::object; + j.m_value.object = j.template create(begin(obj), end(obj)); + j.assert_invariant(); + } +}; + +///////////// +// to_json // +///////////// + +template::value, int> = 0> +void to_json(BasicJsonType& j, T b) noexcept +{ + external_constructor::construct(j, b); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleString& s) +{ + external_constructor::construct(j, s); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +{ + external_constructor::construct(j, std::move(s)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, FloatType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, EnumType e) noexcept +{ + using underlying_type = typename std::underlying_type::type; + external_constructor::construct(j, static_cast(e)); +} + +template +void to_json(BasicJsonType& j, const std::vector& e) +{ + external_constructor::construct(j, e); +} + +template::value or + std::is_same::value, + int> = 0> +void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +{ + external_constructor::construct(j, arr); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, std::valarray arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +{ + external_constructor::construct(j, obj); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +{ + external_constructor::construct(j, std::move(obj)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, T (&arr)[N]) +{ + external_constructor::construct(j, arr); +} + +template +void to_json(BasicJsonType& j, const std::pair& p) +{ + j = {p.first, p.second}; +} + +template +void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence) +{ + j = {std::get(t)...}; +} + +template +void to_json(BasicJsonType& j, const std::tuple& t) +{ + to_json_tuple_impl(j, t, index_sequence_for {}); +} + +struct to_json_fn +{ + private: + template + auto call(BasicJsonType& j, T&& val, priority_tag<1> /*unused*/) const noexcept(noexcept(to_json(j, std::forward(val)))) + -> decltype(to_json(j, std::forward(val)), void()) + { + return to_json(j, std::forward(val)); + } + + template + void call(BasicJsonType& /*unused*/, T&& /*unused*/, priority_tag<0> /*unused*/) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find to_json() method in T's namespace"); + +#ifdef _MSC_VER + // MSVC does not show a stacktrace for the above assert + using decayed = uncvref_t; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif + } + + public: + template + void operator()(BasicJsonType& j, T&& val) const + noexcept(noexcept(std::declval().call(j, std::forward(val), priority_tag<1> {}))) + { + return call(j, std::forward(val), priority_tag<1> {}); + } +}; +} + +/// namespace to hold default `to_json` function +namespace +{ +constexpr const auto& to_json = detail::static_const::value; +} +} + +// #include + + +#include // min +#include // array +#include // assert +#include // size_t +#include // strlen +#include // streamsize, streamoff, streampos +#include // istream +#include // begin, end, iterator_traits, random_access_iterator_tag, distance, next +#include // shared_ptr, make_shared, addressof +#include // accumulate +#include // string, char_traits +#include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer +#include // pair, declval + +// #include + + +namespace nlohmann +{ +namespace detail +{ +//////////////////// +// input adapters // +//////////////////// + +/*! +@brief abstract input adapter interface + +Produces a stream of std::char_traits::int_type characters from a +std::istream, a buffer, or some other input type. Accepts the return of exactly +one non-EOF character for future input. The int_type characters returned +consist of all valid char values as positive values (typically unsigned char), +plus an EOF value outside that range, specified by the value of the function +std::char_traits::eof(). This value is typically -1, but could be any +arbitrary value which is not a valid char value. +*/ +struct input_adapter_protocol +{ + /// get a character [0,255] or std::char_traits::eof(). + virtual std::char_traits::int_type get_character() = 0; + /// restore the last non-eof() character to input + virtual void unget_character() = 0; + virtual ~input_adapter_protocol() = default; +}; + +/// a type to simplify interfaces +using input_adapter_t = std::shared_ptr; + +/*! +Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at +beginning of input. Does not support changing the underlying std::streambuf +in mid-input. Maintains underlying std::istream and std::streambuf to support +subsequent use of standard std::istream operations to process any input +characters following those used in parsing the JSON input. Clears the +std::istream flags; any input errors (e.g., EOF) will be detected by the first +subsequent call for input from the std::istream. +*/ +class input_stream_adapter : public input_adapter_protocol +{ + public: + ~input_stream_adapter() override + { + // clear stream flags; we use underlying streambuf I/O, do not + // maintain ifstream flags + is.clear(); + } + + explicit input_stream_adapter(std::istream& i) + : is(i), sb(*i.rdbuf()) + { + // skip byte order mark + std::char_traits::int_type c; + if ((c = get_character()) == 0xEF) + { + if ((c = get_character()) == 0xBB) + { + if ((c = get_character()) == 0xBF) + { + return; // Ignore BOM + } + else if (c != std::char_traits::eof()) + { + is.unget(); + } + is.putback('\xBB'); + } + else if (c != std::char_traits::eof()) + { + is.unget(); + } + is.putback('\xEF'); + } + else if (c != std::char_traits::eof()) + { + is.unget(); // no byte order mark; process as usual + } + } + + // delete because of pointer members + input_stream_adapter(const input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&) = delete; + + // std::istream/std::streambuf use std::char_traits::to_int_type, to + // ensure that std::char_traits::eof() and the character 0xFF do not + // end up as the same value, eg. 0xFFFFFFFF. + std::char_traits::int_type get_character() override + { + return sb.sbumpc(); + } + + void unget_character() override + { + sb.sungetc(); // is.unget() avoided for performance + } + + private: + /// the associated input stream + std::istream& is; + std::streambuf& sb; +}; + +/// input adapter for buffer input +class input_buffer_adapter : public input_adapter_protocol +{ + public: + input_buffer_adapter(const char* b, const std::size_t l) + : cursor(b), limit(b + l), start(b) + { + // skip byte order mark + if (l >= 3 and b[0] == '\xEF' and b[1] == '\xBB' and b[2] == '\xBF') + { + cursor += 3; + } + } + + // delete because of pointer members + input_buffer_adapter(const input_buffer_adapter&) = delete; + input_buffer_adapter& operator=(input_buffer_adapter&) = delete; + + std::char_traits::int_type get_character() noexcept override + { + if (JSON_LIKELY(cursor < limit)) + { + return std::char_traits::to_int_type(*(cursor++)); + } + + return std::char_traits::eof(); + } + + void unget_character() noexcept override + { + if (JSON_LIKELY(cursor > start)) + { + --cursor; + } + } + + private: + /// pointer to the current character + const char* cursor; + /// pointer past the last character + const char* limit; + /// pointer to the first character + const char* start; +}; + +class input_adapter +{ + public: + // native support + + /// input adapter for input stream + input_adapter(std::istream& i) + : ia(std::make_shared(i)) {} + + /// input adapter for input stream + input_adapter(std::istream&& i) + : ia(std::make_shared(i)) {} + + /// input adapter for buffer + template::value and + std::is_integral::type>::value and + sizeof(typename std::remove_pointer::type) == 1, + int>::type = 0> + input_adapter(CharT b, std::size_t l) + : ia(std::make_shared(reinterpret_cast(b), l)) {} + + // derived support + + /// input adapter for string literal + template::value and + std::is_integral::type>::value and + sizeof(typename std::remove_pointer::type) == 1, + int>::type = 0> + input_adapter(CharT b) + : input_adapter(reinterpret_cast(b), + std::strlen(reinterpret_cast(b))) {} + + /// input adapter for iterator range with contiguous storage + template::iterator_category, std::random_access_iterator_tag>::value, + int>::type = 0> + input_adapter(IteratorType first, IteratorType last) + { + // assertion to check that the iterator range is indeed contiguous, + // see http://stackoverflow.com/a/35008842/266378 for more discussion + assert(std::accumulate( + first, last, std::pair(true, 0), + [&first](std::pair res, decltype(*first) val) + { + res.first &= (val == *(std::next(std::addressof(*first), res.second++))); + return res; + }).first); + + // assertion to check that each element is 1 byte long + static_assert( + sizeof(typename std::iterator_traits::value_type) == 1, + "each element in the iterator range must have the size of 1 byte"); + + const auto len = static_cast(std::distance(first, last)); + if (JSON_LIKELY(len > 0)) + { + // there is at least one element: use the address of first + ia = std::make_shared(reinterpret_cast(&(*first)), len); + } + else + { + // the address of first cannot be used: use nullptr + ia = std::make_shared(nullptr, len); + } + } + + /// input adapter for array + template + input_adapter(T (&array)[N]) + : input_adapter(std::begin(array), std::end(array)) {} + + /// input adapter for contiguous container + template::value and + std::is_base_of()))>::iterator_category>::value, + int>::type = 0> + input_adapter(const ContiguousContainer& c) + : input_adapter(std::begin(c), std::end(c)) {} + + operator input_adapter_t() + { + return ia; + } + + private: + /// the actual adapter + input_adapter_t ia = nullptr; +}; +} +} + +// #include + + +#include // localeconv +#include // size_t +#include // strtof, strtod, strtold, strtoll, strtoull +#include // initializer_list +#include // hex, uppercase +#include // setw, setfill +#include // stringstream +#include // char_traits, string +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////// +// lexer // +/////////// + +/*! +@brief lexical analysis + +This class organizes the lexical analysis during JSON deserialization. +*/ +template +class lexer +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value + value_integer, ///< a signed integer -- use get_number_integer() for actual value + value_float, ///< an floating point number -- use get_number_float() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input, ///< indicating the end of the input buffer + literal_or_value ///< a literal or the begin of a value (only for diagnostics) + }; + + /// return name of values of type token_type (only used for errors) + static const char* token_type_name(const token_type t) noexcept + { + switch (t) + { + case token_type::uninitialized: + return ""; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case lexer::token_type::value_unsigned: + case lexer::token_type::value_integer: + case lexer::token_type::value_float: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return ""; + case token_type::end_of_input: + return "end of input"; + case token_type::literal_or_value: + return "'[', '{', or a literal"; + default: // catch non-enum values + return "unknown token"; // LCOV_EXCL_LINE + } + } + + explicit lexer(detail::input_adapter_t adapter) + : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {} + + // delete because of pointer members + lexer(const lexer&) = delete; + lexer& operator=(lexer&) = delete; + + private: + ///////////////////// + // locales + ///////////////////// + + /// return the locale-dependent decimal point + static char get_decimal_point() noexcept + { + const auto loc = localeconv(); + assert(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); + } + + ///////////////////// + // scan functions + ///////////////////// + + /*! + @brief get codepoint from 4 hex characters following `\u` + + For input "\u c1 c2 c3 c4" the codepoint is: + (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 + = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) + + Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' + must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The + conversion is done by subtracting the offset (0x30, 0x37, and 0x57) + between the ASCII value of the character and the desired integer value. + + @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or + non-hex character) + */ + int get_codepoint() + { + // this function only makes sense after reading `\u` + assert(current == 'u'); + int codepoint = 0; + + const auto factors = { 12, 8, 4, 0 }; + for (const auto factor : factors) + { + get(); + + if (current >= '0' and current <= '9') + { + codepoint += ((current - 0x30) << factor); + } + else if (current >= 'A' and current <= 'F') + { + codepoint += ((current - 0x37) << factor); + } + else if (current >= 'a' and current <= 'f') + { + codepoint += ((current - 0x57) << factor); + } + else + { + return -1; + } + } + + assert(0x0000 <= codepoint and codepoint <= 0xFFFF); + return codepoint; + } + + /*! + @brief check if the next byte(s) are inside a given range + + Adds the current byte and, for each passed range, reads a new byte and + checks if it is inside the range. If a violation was detected, set up an + error message and return false. Otherwise, return true. + + @param[in] ranges list of integers; interpreted as list of pairs of + inclusive lower and upper bound, respectively + + @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, + 1, 2, or 3 pairs. This precondition is enforced by an assertion. + + @return true if and only if no range violation was detected + */ + bool next_byte_in_range(std::initializer_list ranges) + { + assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6); + add(current); + + for (auto range = ranges.begin(); range != ranges.end(); ++range) + { + get(); + if (JSON_LIKELY(*range <= current and current <= *(++range))) + { + add(current); + } + else + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; + } + } + + return true; + } + + /*! + @brief scan a string literal + + This function scans a string according to Sect. 7 of RFC 7159. While + scanning, bytes are escaped and copied into buffer token_buffer. Then the + function returns successfully, token_buffer is *not* null-terminated (as it + may contain \0 bytes), and token_buffer.size() is the number of bytes in the + string. + + @return token_type::value_string if string could be successfully scanned, + token_type::parse_error otherwise + + @note In case of errors, variable error_message contains a textual + description. + */ + token_type scan_string() + { + // reset token_buffer (ignore opening quote) + reset(); + + // we entered the function by reading an open quote + assert(current == '\"'); + + while (true) + { + // get next character + switch (get()) + { + // end of file while parsing string + case std::char_traits::eof(): + { + error_message = "invalid string: missing closing quote"; + return token_type::parse_error; + } + + // closing quote + case '\"': + { + return token_type::value_string; + } + + // escapes + case '\\': + { + switch (get()) + { + // quotation mark + case '\"': + add('\"'); + break; + // reverse solidus + case '\\': + add('\\'); + break; + // solidus + case '/': + add('/'); + break; + // backspace + case 'b': + add('\b'); + break; + // form feed + case 'f': + add('\f'); + break; + // line feed + case 'n': + add('\n'); + break; + // carriage return + case 'r': + add('\r'); + break; + // tab + case 't': + add('\t'); + break; + + // unicode escapes + case 'u': + { + const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; // start with codepoint1 + + if (JSON_UNLIKELY(codepoint1 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if code point is a high surrogate + if (0xD800 <= codepoint1 and codepoint1 <= 0xDBFF) + { + // expect next \uxxxx entry + if (JSON_LIKELY(get() == '\\' and get() == 'u')) + { + const int codepoint2 = get_codepoint(); + + if (JSON_UNLIKELY(codepoint2 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if codepoint2 is a low surrogate + if (JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF)) + { + // overwrite codepoint + codepoint = + // high surrogate occupies the most significant 22 bits + (codepoint1 << 10) + // low surrogate occupies the least significant 15 bits + + codepoint2 + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00; + } + else + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + if (JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF)) + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; + return token_type::parse_error; + } + } + + // result of the above calculation yields a proper codepoint + assert(0x00 <= codepoint and codepoint <= 0x10FFFF); + + // translate codepoint into bytes + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + add(codepoint); + } + else if (codepoint <= 0x7FF) + { + // 2-byte characters: 110xxxxx 10xxxxxx + add(0xC0 | (codepoint >> 6)); + add(0x80 | (codepoint & 0x3F)); + } + else if (codepoint <= 0xFFFF) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + add(0xE0 | (codepoint >> 12)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } + else + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + add(0xF0 | (codepoint >> 18)); + add(0x80 | ((codepoint >> 12) & 0x3F)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } + + break; + } + + // other characters after escape + default: + error_message = "invalid string: forbidden character after backslash"; + return token_type::parse_error; + } + + break; + } + + // invalid control characters + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + { + error_message = "invalid string: control character must be escaped"; + return token_type::parse_error; + } + + // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) + case 0x20: + case 0x21: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + { + add(current); + break; + } + + // U+0080..U+07FF: bytes C2..DF 80..BF + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: + { + if (JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF}))) + { + return token_type::parse_error; + } + break; + } + + // U+0800..U+0FFF: bytes E0 A0..BF 80..BF + case 0xE0: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF + // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xEE: + case 0xEF: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+D000..U+D7FF: bytes ED 80..9F 80..BF + case 0xED: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF + case 0xF0: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF + case 0xF1: + case 0xF2: + case 0xF3: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF + case 0xF4: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // remaining bytes (80..C1 and F5..FF) are ill-formed + default: + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return token_type::parse_error; + } + } + } + } + + static void strtof(float& f, const char* str, char** endptr) noexcept + { + f = std::strtof(str, endptr); + } + + static void strtof(double& f, const char* str, char** endptr) noexcept + { + f = std::strtod(str, endptr); + } + + static void strtof(long double& f, const char* str, char** endptr) noexcept + { + f = std::strtold(str, endptr); + } + + /*! + @brief scan a number literal + + This function scans a string according to Sect. 6 of RFC 7159. + + The function is realized with a deterministic finite state machine derived + from the grammar described in RFC 7159. Starting in state "init", the + input is read and used to determined the next state. Only state "done" + accepts the number. State "error" is a trap state to model errors. In the + table below, "anything" means any character but the ones listed before. + + state | 0 | 1-9 | e E | + | - | . | anything + ---------|----------|----------|----------|---------|---------|----------|----------- + init | zero | any1 | [error] | [error] | minus | [error] | [error] + minus | zero | any1 | [error] | [error] | [error] | [error] | [error] + zero | done | done | exponent | done | done | decimal1 | done + any1 | any1 | any1 | exponent | done | done | decimal1 | done + decimal1 | decimal2 | [error] | [error] | [error] | [error] | [error] | [error] + decimal2 | decimal2 | decimal2 | exponent | done | done | done | done + exponent | any2 | any2 | [error] | sign | sign | [error] | [error] + sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] + any2 | any2 | any2 | done | done | done | done | done + + The state machine is realized with one label per state (prefixed with + "scan_number_") and `goto` statements between them. The state machine + contains cycles, but any cycle can be left when EOF is read. Therefore, + the function is guaranteed to terminate. + + During scanning, the read bytes are stored in token_buffer. This string is + then converted to a signed integer, an unsigned integer, or a + floating-point number. + + @return token_type::value_unsigned, token_type::value_integer, or + token_type::value_float if number could be successfully scanned, + token_type::parse_error otherwise + + @note The scanner is independent of the current locale. Internally, the + locale's decimal point is used instead of `.` to work with the + locale-dependent converters. + */ + token_type scan_number() + { + // reset token_buffer to store the number's bytes + reset(); + + // the type of the parsed number; initially set to unsigned; will be + // changed if minus sign, decimal point or exponent is read + token_type number_type = token_type::value_unsigned; + + // state (init): we just found out we need to scan a number + switch (current) + { + case '-': + { + add(current); + goto scan_number_minus; + } + + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + // all other characters are rejected outside scan_number() + assert(false); // LCOV_EXCL_LINE + } + } + +scan_number_minus: + // state: we just parsed a leading minus sign + number_type = token_type::value_integer; + switch (get()) + { + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + error_message = "invalid number; expected digit after '-'"; + return token_type::parse_error; + } + } + +scan_number_zero: + // state: we just parse a zero (maybe with a leading minus sign) + switch (get()) + { + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_any1: + // state: we just parsed a number 0-9 (maybe with a leading minus sign) + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_decimal1: + // state: we just parsed a decimal point + number_type = token_type::value_float; + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + default: + { + error_message = "invalid number; expected digit after '.'"; + return token_type::parse_error; + } + } + +scan_number_decimal2: + // we just parsed at least one number after a decimal point + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_exponent: + // we just parsed an exponent + number_type = token_type::value_float; + switch (get()) + { + case '+': + case '-': + { + add(current); + goto scan_number_sign; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = + "invalid number; expected '+', '-', or digit after exponent"; + return token_type::parse_error; + } + } + +scan_number_sign: + // we just parsed an exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = "invalid number; expected digit after exponent sign"; + return token_type::parse_error; + } + } + +scan_number_any2: + // we just parsed a number after the exponent or exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + goto scan_number_done; + } + +scan_number_done: + // unget the character after the number (we only read it to know that + // we are done scanning a number) + unget(); + + char* endptr = nullptr; + errno = 0; + + // try to parse integers first and fall back to floats + if (number_type == token_type::value_unsigned) + { + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_unsigned = static_cast(x); + if (value_unsigned == x) + { + return token_type::value_unsigned; + } + } + } + else if (number_type == token_type::value_integer) + { + const auto x = std::strtoll(token_buffer.data(), &endptr, 10); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_integer = static_cast(x); + if (value_integer == x) + { + return token_type::value_integer; + } + } + } + + // this code is reached if we parse a floating-point number or if an + // integer conversion above failed + strtof(value_float, token_buffer.data(), &endptr); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + + return token_type::value_float; + } + + /*! + @param[in] literal_text the literal text to expect + @param[in] length the length of the passed literal text + @param[in] return_type the token type to return on success + */ + token_type scan_literal(const char* literal_text, const std::size_t length, + token_type return_type) + { + assert(current == literal_text[0]); + for (std::size_t i = 1; i < length; ++i) + { + if (JSON_UNLIKELY(get() != literal_text[i])) + { + error_message = "invalid literal"; + return token_type::parse_error; + } + } + return return_type; + } + + ///////////////////// + // input management + ///////////////////// + + /// reset token_buffer; current character is beginning of token + void reset() noexcept + { + token_buffer.clear(); + token_string.clear(); + token_string.push_back(std::char_traits::to_char_type(current)); + } + + /* + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a + `std::char_traits::eof()` in that case. Stores the scanned characters + for use in error messages. + + @return character read from the input + */ + std::char_traits::int_type get() + { + ++chars_read; + current = ia->get_character(); + if (JSON_LIKELY(current != std::char_traits::eof())) + { + token_string.push_back(std::char_traits::to_char_type(current)); + } + return current; + } + + /// unget current character (return it again on next get) + void unget() + { + --chars_read; + if (JSON_LIKELY(current != std::char_traits::eof())) + { + ia->unget_character(); + assert(token_string.size() != 0); + token_string.pop_back(); + } + } + + /// add a character to token_buffer + void add(int c) + { + token_buffer.push_back(std::char_traits::to_char_type(c)); + } + + public: + ///////////////////// + // value getters + ///////////////////// + + /// return integer value + constexpr number_integer_t get_number_integer() const noexcept + { + return value_integer; + } + + /// return unsigned integer value + constexpr number_unsigned_t get_number_unsigned() const noexcept + { + return value_unsigned; + } + + /// return floating-point value + constexpr number_float_t get_number_float() const noexcept + { + return value_float; + } + + /// return current string value (implicitly resets the token; useful only once) + std::string move_string() + { + return std::move(token_buffer); + } + + ///////////////////// + // diagnostics + ///////////////////// + + /// return position of last read token + constexpr std::size_t get_position() const noexcept + { + return chars_read; + } + + /// return the last read token (for errors only). Will never contain EOF + /// (an arbitrary value that is not a valid char value, often -1), because + /// 255 may legitimately occur. May contain NUL, which should be escaped. + std::string get_token_string() const + { + // escape control characters + std::string result; + for (const auto c : token_string) + { + if ('\x00' <= c and c <= '\x1F') + { + // escape control characters + std::stringstream ss; + ss << "(c) << ">"; + result += ss.str(); + } + else + { + // add character as is + result.push_back(c); + } + } + + return result; + } + + /// return syntax error message + constexpr const char* get_error_message() const noexcept + { + return error_message; + } + + ///////////////////// + // actual scanner + ///////////////////// + + token_type scan() + { + // read next character and ignore whitespace + do + { + get(); + } + while (current == ' ' or current == '\t' or current == '\n' or current == '\r'); + + switch (current) + { + // structural characters + case '[': + return token_type::begin_array; + case ']': + return token_type::end_array; + case '{': + return token_type::begin_object; + case '}': + return token_type::end_object; + case ':': + return token_type::name_separator; + case ',': + return token_type::value_separator; + + // literals + case 't': + return scan_literal("true", 4, token_type::literal_true); + case 'f': + return scan_literal("false", 5, token_type::literal_false); + case 'n': + return scan_literal("null", 4, token_type::literal_null); + + // string + case '\"': + return scan_string(); + + // number + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scan_number(); + + // end of input (the null byte is needed when parsing from + // string literals) + case '\0': + case std::char_traits::eof(): + return token_type::end_of_input; + + // error + default: + error_message = "invalid literal"; + return token_type::parse_error; + } + } + + private: + /// input adapter + detail::input_adapter_t ia = nullptr; + + /// the current character + std::char_traits::int_type current = std::char_traits::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// raw input token string (for error messages) + std::vector token_string {}; + + /// buffer for variable-length tokens (numbers, strings) + std::string token_buffer {}; + + /// a description of occurred lexer errors + const char* error_message = ""; + + // number values + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; + + /// the decimal point + const char decimal_point_char = '.'; +}; +} +} + +// #include + + +#include // assert +#include // isfinite +#include // uint8_t +#include // function +#include // string +#include // move + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +//////////// +// parser // +//////////// + +/*! +@brief syntax analysis + +This class implements a recursive decent parser. +*/ +template +class parser +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using lexer_t = lexer; + using token_type = typename lexer_t::token_type; + + public: + enum class parse_event_t : uint8_t + { + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value + }; + + using parser_callback_t = + std::function; + + /// a parser reading from an input adapter + explicit parser(detail::input_adapter_t adapter, + const parser_callback_t cb = nullptr, + const bool allow_exceptions_ = true) + : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_) + {} + + /*! + @brief public parser interface + + @param[in] strict whether to expect the last token to be EOF + @param[in,out] result parsed JSON value + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse(const bool strict, BasicJsonType& result) + { + // read first token + get_token(); + + parse_internal(true, result); + result.assert_invariant(); + + // in strict mode, input must be completely read + if (strict) + { + get_token(); + expect(token_type::end_of_input); + } + + // in case of an error, return discarded value + if (errored) + { + result = value_t::discarded; + return; + } + + // set top-level value to null if it was discarded by the callback + // function + if (result.is_discarded()) + { + result = nullptr; + } + } + + /*! + @brief public accept interface + + @param[in] strict whether to expect the last token to be EOF + @return whether the input is a proper JSON text + */ + bool accept(const bool strict = true) + { + // read first token + get_token(); + + if (not accept_internal()) + { + return false; + } + + // strict => last token must be EOF + return not strict or (get_token() == token_type::end_of_input); + } + + private: + /*! + @brief the actual parser + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse_internal(bool keep, BasicJsonType& result) + { + // never parse after a parse error was detected + assert(not errored); + + // start with a discarded value + if (not result.is_discarded()) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + + switch (last_token) + { + case token_type::begin_object: + { + if (keep) + { + if (callback) + { + keep = callback(depth++, parse_event_t::object_start, result); + } + + if (not callback or keep) + { + // explicitly set result to object to cope with {} + result.m_type = value_t::object; + result.m_value = value_t::object; + } + } + + // read next token + get_token(); + + // closing } -> we are done + if (last_token == token_type::end_object) + { + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + // parse values + std::string key; + BasicJsonType value; + while (true) + { + // store key + if (not expect(token_type::value_string)) + { + return; + } + key = m_lexer.move_string(); + + bool keep_tag = false; + if (keep) + { + if (callback) + { + BasicJsonType k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } + else + { + keep_tag = true; + } + } + + // parse separator (:) + get_token(); + if (not expect(token_type::name_separator)) + { + return; + } + + // parse and add value + get_token(); + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if (JSON_UNLIKELY(errored)) + { + return; + } + + if (keep and keep_tag and not value.is_discarded()) + { + result.m_value.object->emplace(std::move(key), std::move(value)); + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing } + if (not expect(token_type::end_object)) + { + return; + } + break; + } + + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + case token_type::begin_array: + { + if (keep) + { + if (callback) + { + keep = callback(depth++, parse_event_t::array_start, result); + } + + if (not callback or keep) + { + // explicitly set result to array to cope with [] + result.m_type = value_t::array; + result.m_value = value_t::array; + } + } + + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == token_type::end_array) + { + if (callback and not callback(--depth, parse_event_t::array_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + // parse values + BasicJsonType value; + while (true) + { + // parse value + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if (JSON_UNLIKELY(errored)) + { + return; + } + + if (keep and not value.is_discarded()) + { + result.m_value.array->push_back(std::move(value)); + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing ] + if (not expect(token_type::end_array)) + { + return; + } + break; + } + + if (keep and callback and not callback(--depth, parse_event_t::array_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + case token_type::literal_null: + { + result.m_type = value_t::null; + break; + } + + case token_type::value_string: + { + result.m_type = value_t::string; + result.m_value = m_lexer.move_string(); + break; + } + + case token_type::literal_true: + { + result.m_type = value_t::boolean; + result.m_value = true; + break; + } + + case token_type::literal_false: + { + result.m_type = value_t::boolean; + result.m_value = false; + break; + } + + case token_type::value_unsigned: + { + result.m_type = value_t::number_unsigned; + result.m_value = m_lexer.get_number_unsigned(); + break; + } + + case token_type::value_integer: + { + result.m_type = value_t::number_integer; + result.m_value = m_lexer.get_number_integer(); + break; + } + + case token_type::value_float: + { + result.m_type = value_t::number_float; + result.m_value = m_lexer.get_number_float(); + + // throw in case of infinity or NAN + if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float))) + { + if (allow_exceptions) + { + JSON_THROW(out_of_range::create(406, "number overflow parsing '" + + m_lexer.get_token_string() + "'")); + } + expect(token_type::uninitialized); + } + break; + } + + case token_type::parse_error: + { + // using "uninitialized" to avoid "expected" message + if (not expect(token_type::uninitialized)) + { + return; + } + break; // LCOV_EXCL_LINE + } + + default: + { + // the last token was unexpected; we expected a value + if (not expect(token_type::literal_or_value)) + { + return; + } + break; // LCOV_EXCL_LINE + } + } + + if (keep and callback and not callback(depth, parse_event_t::value, result)) + { + result.m_type = value_t::discarded; + } + } + + /*! + @brief the actual acceptor + + @invariant 1. The last token is not yet processed. Therefore, the caller + of this function must make sure a token has been read. + 2. When this function returns, the last token is processed. + That is, the last read character was already considered. + + This invariant makes sure that no token needs to be "unput". + */ + bool accept_internal() + { + switch (last_token) + { + case token_type::begin_object: + { + // read next token + get_token(); + + // closing } -> we are done + if (last_token == token_type::end_object) + { + return true; + } + + // parse values + while (true) + { + // parse key + if (last_token != token_type::value_string) + { + return false; + } + + // parse separator (:) + get_token(); + if (last_token != token_type::name_separator) + { + return false; + } + + // parse value + get_token(); + if (not accept_internal()) + { + return false; + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing } + return (last_token == token_type::end_object); + } + } + + case token_type::begin_array: + { + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == token_type::end_array) + { + return true; + } + + // parse values + while (true) + { + // parse value + if (not accept_internal()) + { + return false; + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing ] + return (last_token == token_type::end_array); + } + } + + case token_type::value_float: + { + // reject infinity or NAN + return std::isfinite(m_lexer.get_number_float()); + } + + case token_type::literal_false: + case token_type::literal_null: + case token_type::literal_true: + case token_type::value_integer: + case token_type::value_string: + case token_type::value_unsigned: + return true; + + default: // the last token was unexpected + return false; + } + } + + /// get next token from lexer + token_type get_token() + { + return (last_token = m_lexer.scan()); + } + + /*! + @throw parse_error.101 if expected token did not occur + */ + bool expect(token_type t) + { + if (JSON_UNLIKELY(t != last_token)) + { + errored = true; + expected = t; + if (allow_exceptions) + { + throw_exception(); + } + else + { + return false; + } + } + + return true; + } + + [[noreturn]] void throw_exception() const + { + std::string error_msg = "syntax error - "; + if (last_token == token_type::parse_error) + { + error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + + m_lexer.get_token_string() + "'"; + } + else + { + error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); + } + + if (expected != token_type::uninitialized) + { + error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); + } + + JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg)); + } + + private: + /// current level of recursion + int depth = 0; + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + token_type last_token = token_type::uninitialized; + /// the lexer + lexer_t m_lexer; + /// whether a syntax error occurred + bool errored = false; + /// possible reason for the syntax error + token_type expected = token_type::uninitialized; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; +} +} + +// #include + + +#include // ptrdiff_t +#include // numeric_limits + +namespace nlohmann +{ +namespace detail +{ +/* +@brief an iterator for primitive JSON types + +This class models an iterator for primitive JSON types (boolean, number, +string). It's only purpose is to allow the iterator/const_iterator classes +to "iterate" over primitive values. Internally, the iterator is modeled by +a `difference_type` variable. Value begin_value (`0`) models the begin, +end_value (`1`) models past the end. +*/ +class primitive_iterator_t +{ + private: + using difference_type = std::ptrdiff_t; + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + /// iterator as signed integer type + difference_type m_it = (std::numeric_limits::min)(); + + public: + constexpr difference_type get_value() const noexcept + { + return m_it; + } + + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return m_it == begin_value; + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return m_it == end_value; + } + + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it == rhs.m_it; + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + primitive_iterator_t operator+(difference_type n) noexcept + { + auto result = *this; + result += n; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + primitive_iterator_t& operator++() noexcept + { + ++m_it; + return *this; + } + + primitive_iterator_t const operator++(int) noexcept + { + auto result = *this; + m_it++; + return result; + } + + primitive_iterator_t& operator--() noexcept + { + --m_it; + return *this; + } + + primitive_iterator_t const operator--(int) noexcept + { + auto result = *this; + m_it--; + return result; + } + + primitive_iterator_t& operator+=(difference_type n) noexcept + { + m_it += n; + return *this; + } + + primitive_iterator_t& operator-=(difference_type n) noexcept + { + m_it -= n; + return *this; + } +}; +} +} + +// #include + + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/*! +@brief an iterator value + +@note This structure could easily be a union, but MSVC currently does not allow +unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. +*/ +template struct internal_iterator +{ + /// iterator for JSON objects + typename BasicJsonType::object_t::iterator object_iterator {}; + /// iterator for JSON arrays + typename BasicJsonType::array_t::iterator array_iterator {}; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator {}; +}; +} +} + +// #include + + +#include // not +#include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next +#include // conditional, is_const, remove_const + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// forward declare, to be able to friend it later on +template class iteration_proxy; + +/*! +@brief a template for a bidirectional iterator for the @ref basic_json class + +This class implements a both iterators (iterator and const_iterator) for the +@ref basic_json class. + +@note An iterator is called *initialized* when a pointer to a JSON value has + been set (e.g., by a constructor or a copy assignment). If the iterator is + default-constructed, it is *uninitialized* and most methods are undefined. + **The library uses assertions to detect calls on uninitialized iterators.** + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). + +@since version 1.0.0, simplified in version 2.0.9, change to bidirectional + iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) +*/ +template +class iter_impl +{ + /// allow basic_json to access private members + friend iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; + friend BasicJsonType; + friend iteration_proxy; + + using object_t = typename BasicJsonType::object_t; + using array_t = typename BasicJsonType::array_t; + // make sure BasicJsonType is basic_json or const basic_json + static_assert(is_basic_json::type>::value, + "iter_impl only accepts (const) basic_json"); + + public: + + /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. + /// The C++ Standard has never required user-defined iterators to derive from std::iterator. + /// A user-defined iterator should provide publicly accessible typedefs named + /// iterator_category, value_type, difference_type, pointer, and reference. + /// Note that value_type is required to be non-const, even for constant iterators. + using iterator_category = std::bidirectional_iterator_tag; + + /// the type of the values when the iterator is dereferenced + using value_type = typename BasicJsonType::value_type; + /// a type to represent differences between iterators + using difference_type = typename BasicJsonType::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename std::conditional::value, + typename BasicJsonType::const_pointer, + typename BasicJsonType::pointer>::type; + /// defines a reference to the type iterated over (value_type) + using reference = + typename std::conditional::value, + typename BasicJsonType::const_reference, + typename BasicJsonType::reference>::type; + + /// default constructor + iter_impl() = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit iter_impl(pointer object) noexcept : m_object(object) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /*! + @note The conventional copy constructor and copy assignment are implicitly + defined. Combined with the following converting constructor and + assignment, they support: (1) copy from iterator to iterator, (2) + copy from const iterator to const iterator, and (3) conversion from + iterator to const iterator. However conversion from const iterator + to iterator is not defined. + */ + + /*! + @brief converting constructor + @param[in] other non-const iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl(const iter_impl::type>& other) noexcept + : m_object(other.m_object), m_it(other.m_it) {} + + /*! + @brief converting assignment + @param[in,out] other non-const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl::type>& other) noexcept + { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + + private: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) + { + return m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator++(int) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, 1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator--(int) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, -1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator==(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + return (m_it.object_iterator == other.m_it.object_iterator); + + case value_t::array: + return (m_it.array_iterator == other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator!=(const iter_impl& other) const + { + return not operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); + + case value_t::array: + return (m_it.array_iterator < other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const iter_impl& other) const + { + return not other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const iter_impl& other) const + { + return not operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const iter_impl& other) const + { + return not operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief addition of distance and iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + friend iter_impl operator+(difference_type i, const iter_impl& it) + { + auto result = it; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const iter_impl& other) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + return m_it.array_iterator - other.m_it.array_iterator; + + default: + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); + + case value_t::array: + return *std::next(m_it.array_iterator, n); + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.get_value() == -n)) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + typename object_t::key_type key() const + { + assert(m_object != nullptr); + + if (JSON_LIKELY(m_object->is_object())) + { + return m_it.object_iterator->first; + } + + JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + private: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator::type> m_it; +}; +} +} + +// #include + + +#include // size_t +#include // string, to_string + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/// proxy class for the items() function +template class iteration_proxy +{ + private: + /// helper class for iteration + class iteration_proxy_internal + { + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + std::size_t array_index = 0; + + public: + explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {} + + /// dereference operator (needed for range-based for) + iteration_proxy_internal& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_internal& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// inequality operator (needed for range-based for) + bool operator!=(const iteration_proxy_internal& o) const noexcept + { + return anchor != o.anchor; + } + + /// return key of the iterator + std::string key() const + { + assert(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + return std::to_string(array_index); + + // use key from the object + case value_t::object: + return anchor.key(); + + // use an empty key for all primitive types + default: + return ""; + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } + }; + + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) noexcept + : container(cont) {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_internal begin() noexcept + { + return iteration_proxy_internal(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_internal end() noexcept + { + return iteration_proxy_internal(container.end()); + } +}; +} +} + +// #include + + +#include // ptrdiff_t +#include // reverse_iterator +#include // declval + +namespace nlohmann +{ +namespace detail +{ +////////////////////// +// reverse_iterator // +////////////////////// + +/*! +@brief a template for a reverse iterator class + +@tparam Base the base iterator type to reverse. Valid types are @ref +iterator (to create @ref reverse_iterator) and @ref const_iterator (to +create @ref const_reverse_iterator). + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +- [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + +@since version 1.0.0 +*/ +template +class json_reverse_iterator : public std::reverse_iterator +{ + public: + using difference_type = std::ptrdiff_t; + /// shortcut to the reverse iterator adapter + using base_iterator = std::reverse_iterator; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) {} + + /// create reverse iterator from base class + json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} + + /// post-increment (it++) + json_reverse_iterator const operator++(int) + { + return static_cast(base_iterator::operator++(1)); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + return static_cast(base_iterator::operator++()); + } + + /// post-decrement (it--) + json_reverse_iterator const operator--(int) + { + return static_cast(base_iterator::operator--(1)); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + return static_cast(base_iterator::operator--()); + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + return static_cast(base_iterator::operator+=(i)); + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + return static_cast(base_iterator::operator+(i)); + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + return static_cast(base_iterator::operator-(i)); + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return base_iterator(*this) - base_iterator(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + auto key() const -> decltype(std::declval().key()) + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } +}; +} +} + +// #include + + +#include // copy +#include // size_t +#include // streamsize +#include // back_inserter +#include // shared_ptr, make_shared +#include // basic_ostream +#include // basic_string +#include // vector + +namespace nlohmann +{ +namespace detail +{ +/// abstract output adapter interface +template struct output_adapter_protocol +{ + virtual void write_character(CharType c) = 0; + virtual void write_characters(const CharType* s, std::size_t length) = 0; + virtual ~output_adapter_protocol() = default; +}; + +/// a type to simplify interfaces +template +using output_adapter_t = std::shared_ptr>; + +/// output adapter for byte vectors +template +class output_vector_adapter : public output_adapter_protocol +{ + public: + explicit output_vector_adapter(std::vector& vec) : v(vec) {} + + void write_character(CharType c) override + { + v.push_back(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + std::copy(s, s + length, std::back_inserter(v)); + } + + private: + std::vector& v; +}; + +/// output adapter for output streams +template +class output_stream_adapter : public output_adapter_protocol +{ + public: + explicit output_stream_adapter(std::basic_ostream& s) : stream(s) {} + + void write_character(CharType c) override + { + stream.put(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + stream.write(s, static_cast(length)); + } + + private: + std::basic_ostream& stream; +}; + +/// output adapter for basic_string +template +class output_string_adapter : public output_adapter_protocol +{ + public: + explicit output_string_adapter(std::basic_string& s) : str(s) {} + + void write_character(CharType c) override + { + str.push_back(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + str.append(s, length); + } + + private: + std::basic_string& str; +}; + +template +class output_adapter +{ + public: + output_adapter(std::vector& vec) + : oa(std::make_shared>(vec)) {} + + output_adapter(std::basic_ostream& s) + : oa(std::make_shared>(s)) {} + + output_adapter(std::basic_string& s) + : oa(std::make_shared>(s)) {} + + operator output_adapter_t() + { + return oa; + } + + private: + output_adapter_t oa = nullptr; +}; +} +} + +// #include + + +#include // generate_n +#include // array +#include // assert +#include // ldexp +#include // size_t +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // setw, setfill +#include // hex +#include // back_inserter +#include // numeric_limits +#include // stringstream +#include // char_traits, string +#include // make_pair, move + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary reader // +/////////////////// + +/*! +@brief deserialization of CBOR and MessagePack values +*/ +template +class binary_reader +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using string_t = typename BasicJsonType::string_t; + + public: + /*! + @brief create a binary reader + + @param[in] adapter input adapter to read from + */ + explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter)) + { + assert(ia); + } + + /*! + @brief create a JSON value from CBOR input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from CBOR input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_cbor(const bool strict) + { + const auto res = parse_cbor_internal(); + if (strict) + { + get(); + expect_eof(); + } + return res; + } + + /*! + @brief create a JSON value from MessagePack input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from MessagePack input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_msgpack(const bool strict) + { + const auto res = parse_msgpack_internal(); + if (strict) + { + get(); + expect_eof(); + } + return res; + } + + /*! + @brief create a JSON value from UBJSON input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from UBJSON input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_ubjson(const bool strict) + { + const auto res = parse_ubjson_internal(); + if (strict) + { + get_ignore_noop(); + expect_eof(); + } + return res; + } + + /*! + @brief determine system byte order + + @return true if and only if system's byte order is little endian + + @note from http://stackoverflow.com/a/1001328/266378 + */ + static constexpr bool little_endianess(int num = 1) noexcept + { + return (*reinterpret_cast(&num) == 1); + } + + private: + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + BasicJsonType parse_cbor_internal(const bool get_char = true) + { + switch (get_char ? get() : current) + { + // EOF + case std::char_traits::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + return static_cast(current); + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + return get_number(); + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + return get_number(); + + case 0x1A: // Unsigned integer (four-byte uint32_t follows) + return get_number(); + + case 0x1B: // Unsigned integer (eight-byte uint64_t follows) + return get_number(); + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + return static_cast(0x20 - 1 - current); + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + return static_cast(-1) - get_number(); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + return static_cast(-1) - get_number(); + } + + case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) + { + return static_cast(-1) - get_number(); + } + + case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) + { + return static_cast(-1) - + static_cast(get_number()); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7F: // UTF-8 string (indefinite length) + { + return get_cbor_string(); + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + { + return get_cbor_array(current & 0x1F); + } + + case 0x98: // array (one-byte uint8_t for n follows) + { + return get_cbor_array(get_number()); + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + return get_cbor_array(get_number()); + } + + case 0x9A: // array (four-byte uint32_t for n follow) + { + return get_cbor_array(get_number()); + } + + case 0x9B: // array (eight-byte uint64_t for n follow) + { + return get_cbor_array(get_number()); + } + + case 0x9F: // array (indefinite length) + { + BasicJsonType result = value_t::array; + while (get() != 0xFF) + { + result.push_back(parse_cbor_internal(false)); + } + return result; + } + + // map (0x00..0x17 pairs of data items follow) + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + { + return get_cbor_object(current & 0x1F); + } + + case 0xB8: // map (one-byte uint8_t for n follows) + { + return get_cbor_object(get_number()); + } + + case 0xB9: // map (two-byte uint16_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBA: // map (four-byte uint32_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBB: // map (eight-byte uint64_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBF: // map (indefinite length) + { + BasicJsonType result = value_t::object; + while (get() != 0xFF) + { + auto key = get_cbor_string(); + result[key] = parse_cbor_internal(); + } + return result; + } + + case 0xF4: // false + { + return false; + } + + case 0xF5: // true + { + return true; + } + + case 0xF6: // null + { + return value_t::null; + } + + case 0xF9: // Half-Precision Float (two-byte IEEE 754) + { + const int byte1 = get(); + unexpect_eof(); + const int byte2 = get(); + unexpect_eof(); + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const int half = (byte1 << 8) + byte2; + const int exp = (half >> 10) & 0x1F; + const int mant = half & 0x3FF; + double val; + if (exp == 0) + { + val = std::ldexp(mant, -24); + } + else if (exp != 31) + { + val = std::ldexp(mant + 1024, exp - 25); + } + else + { + val = (mant == 0) ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + } + return (half & 0x8000) != 0 ? -val : val; + } + + case 0xFA: // Single-Precision Float (four-byte IEEE 754) + { + return get_number(); + } + + case 0xFB: // Double-Precision Float (eight-byte IEEE 754) + { + return get_number(); + } + + default: // anything else (0xFF is handled inside the other types) + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, "error reading CBOR; last byte: 0x" + ss.str())); + } + } + } + + BasicJsonType parse_msgpack_internal() + { + switch (get()) + { + // EOF + case std::char_traits::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + // positive fixint + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + return static_cast(current); + + // fixmap + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + { + return get_msgpack_object(current & 0x0F); + } + + // fixarray + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + case 0x98: + case 0x99: + case 0x9A: + case 0x9B: + case 0x9C: + case 0x9D: + case 0x9E: + case 0x9F: + { + return get_msgpack_array(current & 0x0F); + } + + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + return get_msgpack_string(); + + case 0xC0: // nil + return value_t::null; + + case 0xC2: // false + return false; + + case 0xC3: // true + return true; + + case 0xCA: // float 32 + return get_number(); + + case 0xCB: // float 64 + return get_number(); + + case 0xCC: // uint 8 + return get_number(); + + case 0xCD: // uint 16 + return get_number(); + + case 0xCE: // uint 32 + return get_number(); + + case 0xCF: // uint 64 + return get_number(); + + case 0xD0: // int 8 + return get_number(); + + case 0xD1: // int 16 + return get_number(); + + case 0xD2: // int 32 + return get_number(); + + case 0xD3: // int 64 + return get_number(); + + case 0xD9: // str 8 + case 0xDA: // str 16 + case 0xDB: // str 32 + return get_msgpack_string(); + + case 0xDC: // array 16 + { + return get_msgpack_array(get_number()); + } + + case 0xDD: // array 32 + { + return get_msgpack_array(get_number()); + } + + case 0xDE: // map 16 + { + return get_msgpack_object(get_number()); + } + + case 0xDF: // map 32 + { + return get_msgpack_object(get_number()); + } + + // positive fixint + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: + case 0xEF: + case 0xF0: + case 0xF1: + case 0xF2: + case 0xF3: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: + case 0xF8: + case 0xF9: + case 0xFA: + case 0xFB: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + return static_cast(current); + + default: // anything else + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading MessagePack; last byte: 0x" + ss.str())); + } + } + } + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + BasicJsonType parse_ubjson_internal(const bool get_char = true) + { + return get_ubjson_value(get_char ? get_ignore_noop() : current); + } + + /*! + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a -'ve valued + `std::char_traits::eof()` in that case. + + @return character read from the input + */ + int get() + { + ++chars_read; + return (current = ia->get_character()); + } + + /*! + @return character read from the input after ignoring all 'N' entries + */ + int get_ignore_noop() + { + do + { + get(); + } + while (current == 'N'); + + return current; + } + + /* + @brief read a number from the input + + @tparam NumberType the type of the number + + @return number of type @a NumberType + + @note This function needs to respect the system's endianess, because + bytes in CBOR and MessagePack are stored in network order (big + endian) and therefore need reordering on little endian systems. + + @throw parse_error.110 if input has less than `sizeof(NumberType)` bytes + */ + template NumberType get_number() + { + // step 1: read input into array with system's byte order + std::array vec; + for (std::size_t i = 0; i < sizeof(NumberType); ++i) + { + get(); + unexpect_eof(); + + // reverse byte order prior to conversion if necessary + if (is_little_endian) + { + vec[sizeof(NumberType) - i - 1] = static_cast(current); + } + else + { + vec[i] = static_cast(current); // LCOV_EXCL_LINE + } + } + + // step 2: convert array into number of type T and return + NumberType result; + std::memcpy(&result, vec.data(), sizeof(NumberType)); + return result; + } + + /*! + @brief create a string by reading characters from the input + + @param[in] len number of bytes to read + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of string memory. + + @return string created by reading @a len bytes + + @throw parse_error.110 if input has less than @a len bytes + */ + template + string_t get_string(const NumberType len) + { + string_t result; + std::generate_n(std::back_inserter(result), len, [this]() + { + get(); + unexpect_eof(); + return static_cast(current); + }); + return result; + } + + /*! + @brief reads a CBOR string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + Additionally, CBOR's strings with indefinite lengths are supported. + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + string_t get_cbor_string() + { + unexpect_eof(); + + switch (current) + { + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + return get_string(current & 0x1F); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + return get_string(get_number()); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + return get_string(get_number()); + } + + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + { + return get_string(get_number()); + } + + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + { + return get_string(get_number()); + } + + case 0x7F: // UTF-8 string (indefinite length) + { + string_t result; + while (get() != 0xFF) + { + result.append(get_cbor_string()); + } + return result; + } + + default: + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, "expected a CBOR string; last byte: 0x" + ss.str())); + } + } + } + + template + BasicJsonType get_cbor_array(const NumberType len) + { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_cbor_internal(); + }); + return result; + } + + template + BasicJsonType get_cbor_object(const NumberType len) + { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() + { + get(); + auto key = get_cbor_string(); + auto val = parse_cbor_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + + /*! + @brief reads a MessagePack string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + string_t get_msgpack_string() + { + unexpect_eof(); + + switch (current) + { + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + { + return get_string(current & 0x1F); + } + + case 0xD9: // str 8 + { + return get_string(get_number()); + } + + case 0xDA: // str 16 + { + return get_string(get_number()); + } + + case 0xDB: // str 32 + { + return get_string(get_number()); + } + + default: + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a MessagePack string; last byte: 0x" + ss.str())); + } + } + } + + template + BasicJsonType get_msgpack_array(const NumberType len) + { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_msgpack_internal(); + }); + return result; + } + + template + BasicJsonType get_msgpack_object(const NumberType len) + { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() + { + get(); + auto key = get_msgpack_string(); + auto val = parse_msgpack_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + + /*! + @brief reads a UBJSON string + + This function is either called after reading the 'S' byte explicitly + indicating a string, or in case of an object key where the 'S' byte can be + left out. + + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + string_t get_ubjson_string(const bool get_char = true) + { + if (get_char) + { + get(); // TODO: may we ignore N here? + } + + unexpect_eof(); + + switch (current) + { + case 'U': + return get_string(get_number()); + case 'i': + return get_string(get_number()); + case 'I': + return get_string(get_number()); + case 'l': + return get_string(get_number()); + case 'L': + return get_string(get_number()); + default: + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a UBJSON string; last byte: 0x" + ss.str())); + } + } + + /*! + @brief determine the type and size for a container + + In the optimized UBJSON format, a type and a size can be provided to allow + for a more compact representation. + + @return pair of the size and the type + */ + std::pair get_ubjson_size_type() + { + std::size_t sz = string_t::npos; + int tc = 0; + + get_ignore_noop(); + + if (current == '$') + { + tc = get(); // must not ignore 'N', because 'N' maybe the type + unexpect_eof(); + + get_ignore_noop(); + if (current != '#') + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "expected '#' after UBJSON type information; last byte: 0x" + ss.str())); + } + sz = parse_ubjson_internal(); + } + else if (current == '#') + { + sz = parse_ubjson_internal(); + } + + return std::make_pair(sz, tc); + } + + BasicJsonType get_ubjson_value(const int prefix) + { + switch (prefix) + { + case std::char_traits::eof(): // EOF + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + case 'T': // true + return true; + case 'F': // false + return false; + + case 'Z': // null + return nullptr; + + case 'U': + return get_number(); + case 'i': + return get_number(); + case 'I': + return get_number(); + case 'l': + return get_number(); + case 'L': + return get_number(); + case 'd': + return get_number(); + case 'D': + return get_number(); + + case 'C': // char + { + get(); + unexpect_eof(); + if (JSON_UNLIKELY(current > 127)) + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + ss.str())); + } + return string_t(1, static_cast(current)); + } + + case 'S': // string + return get_ubjson_string(); + + case '[': // array + return get_ubjson_array(); + + case '{': // object + return get_ubjson_object(); + + default: // anything else + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading UBJSON; last byte: 0x" + ss.str())); + } + } + + BasicJsonType get_ubjson_array() + { + BasicJsonType result = value_t::array; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != string_t::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive array size: " + std::to_string(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this, size_and_type]() + { + return get_ubjson_value(size_and_type.second); + }); + } + } + else + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this]() + { + return parse_ubjson_internal(); + }); + } + } + else + { + while (current != ']') + { + result.push_back(parse_ubjson_internal(false)); + get_ignore_noop(); + } + } + + return result; + } + + BasicJsonType get_ubjson_object() + { + BasicJsonType result = value_t::object; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != string_t::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive object size: " + std::to_string(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this, size_and_type]() + { + auto key = get_ubjson_string(); + auto val = get_ubjson_value(size_and_type.second); + return std::make_pair(std::move(key), std::move(val)); + }); + } + else + { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this]() + { + auto key = get_ubjson_string(); + auto val = parse_ubjson_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + } + } + else + { + while (current != '}') + { + auto key = get_ubjson_string(false); + result[std::move(key)] = parse_ubjson_internal(); + get_ignore_noop(); + } + } + + return result; + } + + /*! + @brief throw if end of input is not reached + @throw parse_error.110 if input not ended + */ + void expect_eof() const + { + if (JSON_UNLIKELY(current != std::char_traits::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "expected end of input")); + } + } + + /*! + @briefthrow if end of input is reached + @throw parse_error.110 if input ended + */ + void unexpect_eof() const + { + if (JSON_UNLIKELY(current == std::char_traits::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + } + } + + private: + /// input adapter + input_adapter_t ia = nullptr; + + /// the current character + int current = std::char_traits::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// whether we can assume little endianess + const bool is_little_endian = little_endianess(); +}; +} +} + +// #include + + +#include // reverse +#include // array +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // numeric_limits + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary writer // +/////////////////// + +/*! +@brief serialization to CBOR and MessagePack values +*/ +template +class binary_writer +{ + public: + /*! + @brief create a binary writer + + @param[in] adapter output adapter to write to + */ + explicit binary_writer(output_adapter_t adapter) : oa(adapter) + { + assert(oa); + } + + /*! + @brief[in] j JSON value to serialize + */ + void write_cbor(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: + { + oa->write_character(static_cast(0xF6)); + break; + } + + case value_t::boolean: + { + oa->write_character(j.m_value.boolean + ? static_cast(0xF5) + : static_cast(0xF4)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_value.number_integer <= 0x17) + { + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x18)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x19)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x1A)); + write_number(static_cast(j.m_value.number_integer)); + } + else + { + oa->write_character(static_cast(0x1B)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + // The conversions below encode the sign in the first + // byte, and the value is converted to a positive number. + const auto positive_number = -1 - j.m_value.number_integer; + if (j.m_value.number_integer >= -24) + { + write_number(static_cast(0x20 + positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x38)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x39)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x3A)); + write_number(static_cast(positive_number)); + } + else + { + oa->write_character(static_cast(0x3B)); + write_number(static_cast(positive_number)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= 0x17) + { + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x18)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x19)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x1A)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else + { + oa->write_character(static_cast(0x1B)); + write_number(static_cast(j.m_value.number_unsigned)); + } + break; + } + + case value_t::number_float: // Double-Precision Float + { + oa->write_character(static_cast(0xFB)); + write_number(j.m_value.number_float); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 0x17) + { + write_number(static_cast(0x60 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x78)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x79)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x7A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x7B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 0x17) + { + write_number(static_cast(0x80 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x98)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x99)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x9A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x9B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_cbor(el); + } + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 0x17) + { + write_number(static_cast(0xA0 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xB8)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xB9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xBA)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xBB)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_cbor(el.first); + write_cbor(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @brief[in] j JSON value to serialize + */ + void write_msgpack(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: // nil + { + oa->write_character(static_cast(0xC0)); + break; + } + + case value_t::boolean: // true and false + { + oa->write_character(j.m_value.boolean + ? static_cast(0xC3) + : static_cast(0xC2)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(static_cast(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(static_cast(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(static_cast(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(static_cast(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + if (j.m_value.number_integer >= -32) + { + // negative fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 8 + oa->write_character(static_cast(0xD0)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 16 + oa->write_character(static_cast(0xD1)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 32 + oa->write_character(static_cast(0xD2)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 64 + oa->write_character(static_cast(0xD3)); + write_number(static_cast(j.m_value.number_integer)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(static_cast(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(static_cast(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(static_cast(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(static_cast(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + break; + } + + case value_t::number_float: // float 64 + { + oa->write_character(static_cast(0xCB)); + write_number(j.m_value.number_float); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 31) + { + // fixstr + write_number(static_cast(0xA0 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 8 + oa->write_character(static_cast(0xD9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 16 + oa->write_character(static_cast(0xDA)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 32 + oa->write_character(static_cast(0xDB)); + write_number(static_cast(N)); + } + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 15) + { + // fixarray + write_number(static_cast(0x90 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 16 + oa->write_character(static_cast(0xDC)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 32 + oa->write_character(static_cast(0xDD)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_msgpack(el); + } + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 15) + { + // fixmap + write_number(static_cast(0x80 | (N & 0xF))); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 16 + oa->write_character(static_cast(0xDE)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 32 + oa->write_character(static_cast(0xDF)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_msgpack(el.first); + write_msgpack(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + @param[in] use_count whether to use '#' prefixes (optimized format) + @param[in] use_type whether to use '$' prefixes (optimized format) + @param[in] add_prefix whether prefixes need to be used for this value + */ + void write_ubjson(const BasicJsonType& j, const bool use_count, + const bool use_type, const bool add_prefix = true) + { + switch (j.type()) + { + case value_t::null: + { + if (add_prefix) + { + oa->write_character(static_cast('Z')); + } + break; + } + + case value_t::boolean: + { + if (add_prefix) + oa->write_character(j.m_value.boolean + ? static_cast('T') + : static_cast('F')); + break; + } + + case value_t::number_integer: + { + write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); + break; + } + + case value_t::number_unsigned: + { + write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); + break; + } + + case value_t::number_float: + { + write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); + break; + } + + case value_t::string: + { + if (add_prefix) + { + oa->write_character(static_cast('S')); + } + write_number_with_ubjson_prefix(j.m_value.string->size(), true); + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + if (add_prefix) + { + oa->write_character(static_cast('[')); + } + + bool prefix_required = true; + if (use_type and not j.m_value.array->empty()) + { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin() + 1, j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(static_cast('$')); + oa->write_character(static_cast(first_prefix)); + } + } + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.array->size(), true); + } + + for (const auto& el : *j.m_value.array) + { + write_ubjson(el, use_count, use_type, prefix_required); + } + + if (not use_count) + { + oa->write_character(static_cast(']')); + } + + break; + } + + case value_t::object: + { + if (add_prefix) + { + oa->write_character(static_cast('{')); + } + + bool prefix_required = true; + if (use_type and not j.m_value.object->empty()) + { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin(), j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(static_cast('$')); + oa->write_character(static_cast(first_prefix)); + } + } + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.object->size(), true); + } + + for (const auto& el : *j.m_value.object) + { + write_number_with_ubjson_prefix(el.first.size(), true); + oa->write_characters( + reinterpret_cast(el.first.c_str()), + el.first.size()); + write_ubjson(el.second, use_count, use_type, prefix_required); + } + + if (not use_count) + { + oa->write_character(static_cast('}')); + } + + break; + } + + default: + break; + } + } + + private: + /* + @brief write a number to output input + + @param[in] n number of type @a NumberType + @tparam NumberType the type of the number + + @note This function needs to respect the system's endianess, because bytes + in CBOR, MessagePack, and UBJSON are stored in network order (big + endian) and therefore need reordering on little endian systems. + */ + template + void write_number(const NumberType n) + { + // step 1: write number to array of length NumberType + std::array vec; + std::memcpy(vec.data(), &n, sizeof(NumberType)); + + // step 2: write array to output (with possible reordering) + if (is_little_endian) + { + // reverse byte order prior to conversion if necessary + std::reverse(vec.begin(), vec.end()); + } + + oa->write_characters(vec.data(), sizeof(NumberType)); + } + + template + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (std::is_floating_point::value) + { + if (add_prefix) + { + oa->write_character(static_cast('D')); // float64 + } + write_number(n); + } + else if (std::is_unsigned::value) + { + if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('i')); // int8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('U')); // uint8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('I')); // int16 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('l')); // int32 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('L')); // int64 + } + write_number(static_cast(n)); + } + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n))); + } + } + else + { + if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('i')); // int8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('U')); // uint8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('I')); // int16 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('l')); // int32 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('L')); // int64 + } + write_number(static_cast(n)); + } + // LCOV_EXCL_START + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n))); + } + // LCOV_EXCL_STOP + } + } + + /*! + @brief determine the type prefix of container values + + @note This function does not need to be 100% accurate when it comes to + integer limits. In case a number exceeds the limits of int64_t, + this will be detected by a later call to function + write_number_with_ubjson_prefix. Therefore, we return 'L' for any + value that does not fit the previous limits. + */ + char ubjson_prefix(const BasicJsonType& j) const noexcept + { + switch (j.type()) + { + case value_t::null: + return 'Z'; + + case value_t::boolean: + return j.m_value.boolean ? 'T' : 'F'; + + case value_t::number_integer: + { + if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_float: + return 'D'; + + case value_t::string: + return 'S'; + + case value_t::array: + return '['; + + case value_t::object: + return '{'; + + default: // discarded values + return 'N'; + } + } + + private: + /// whether we can assume little endianess + const bool is_little_endian = binary_reader::little_endianess(); + + /// the output + output_adapter_t oa = nullptr; +}; +} +} + +// #include + + +#include // reverse, remove, fill, find, none_of +#include // array +#include // assert +#include // and, or +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // setfill +#include // next +#include // numeric_limits +#include // string +#include // stringstream +#include // is_same + +// #include + +// #include + + +#include // assert +#include // or, and, not +#include // signbit, isfinite +#include // intN_t, uintN_t +#include // memcpy, memmove + +namespace nlohmann +{ +namespace detail +{ + +/*! +@brief implements the Grisu2 algorithm for binary to decimal floating-point +conversion. + +This implementation is a slightly modified version of the reference +implementation which may be obtained from +http://florian.loitsch.com/publications (bench.tar.gz). + +The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. + +For a detailed description of the algorithm see: + +[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with + Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming + Language Design and Implementation, PLDI 2010 +[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", + Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language + Design and Implementation, PLDI 1996 +*/ +namespace dtoa_impl +{ + +template +Target reinterpret_bits(const Source source) +{ + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); + + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; +} + +struct diyfp // f * 2^e +{ + static constexpr int kPrecision = 64; // = q + + uint64_t f; + int e; + + constexpr diyfp() noexcept : f(0), e(0) {} + constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + /*! + @brief returns x - y + @pre x.e == y.e and x.f >= y.f + */ + static diyfp sub(const diyfp& x, const diyfp& y) noexcept + { + assert(x.e == y.e); + assert(x.f >= y.f); + + return diyfp(x.f - y.f, x.e); + } + + /*! + @brief returns x * y + @note The result is rounded. (Only the upper q bits are returned.) + */ + static diyfp mul(const diyfp& x, const diyfp& y) noexcept + { + static_assert(kPrecision == 64, "internal error"); + + // Computes: + // f = round((x.f * y.f) / 2^q) + // e = x.e + y.e + q + + // Emulate the 64-bit * 64-bit multiplication: + // + // p = u * v + // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) + // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) + // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) + // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) + // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) + // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) + // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) + // + // (Since Q might be larger than 2^32 - 1) + // + // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) + // + // (Q_hi + H does not overflow a 64-bit int) + // + // = p_lo + 2^64 p_hi + + const uint64_t u_lo = x.f & 0xFFFFFFFF; + const uint64_t u_hi = x.f >> 32; + const uint64_t v_lo = y.f & 0xFFFFFFFF; + const uint64_t v_hi = y.f >> 32; + + const uint64_t p0 = u_lo * v_lo; + const uint64_t p1 = u_lo * v_hi; + const uint64_t p2 = u_hi * v_lo; + const uint64_t p3 = u_hi * v_hi; + + const uint64_t p0_hi = p0 >> 32; + const uint64_t p1_lo = p1 & 0xFFFFFFFF; + const uint64_t p1_hi = p1 >> 32; + const uint64_t p2_lo = p2 & 0xFFFFFFFF; + const uint64_t p2_hi = p2 >> 32; + + uint64_t Q = p0_hi + p1_lo + p2_lo; + + // The full product might now be computed as + // + // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) + // p_lo = p0_lo + (Q << 32) + // + // But in this particular case here, the full p_lo is not required. + // Effectively we only need to add the highest bit in p_lo to p_hi (and + // Q_hi + 1 does not overflow). + + Q += uint64_t{1} << (64 - 32 - 1); // round, ties up + + const uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32); + + return diyfp(h, x.e + y.e + 64); + } + + /*! + @brief normalize x such that the significand is >= 2^(q-1) + @pre x.f != 0 + */ + static diyfp normalize(diyfp x) noexcept + { + assert(x.f != 0); + + while ((x.f >> 63) == 0) + { + x.f <<= 1; + x.e--; + } + + return x; + } + + /*! + @brief normalize x such that the result has the exponent E + @pre e >= x.e and the upper e - x.e bits of x.f must be zero. + */ + static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept + { + const int delta = x.e - target_exponent; + + assert(delta >= 0); + assert(((x.f << delta) >> delta) == x.f); + + return diyfp(x.f << delta, target_exponent); + } +}; + +struct boundaries +{ + diyfp w; + diyfp minus; + diyfp plus; +}; + +/*! +Compute the (normalized) diyfp representing the input number 'value' and its +boundaries. + +@pre value must be finite and positive +*/ +template +boundaries compute_boundaries(FloatType value) +{ + assert(std::isfinite(value)); + assert(value > 0); + + // Convert the IEEE representation into a diyfp. + // + // If v is denormal: + // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) + // If v is normalized: + // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) + + static_assert(std::numeric_limits::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + + constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) + constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); + constexpr int kMinExp = 1 - kBias; + constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1) + + using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type; + + const uint64_t bits = reinterpret_bits(value); + const uint64_t E = bits >> (kPrecision - 1); + const uint64_t F = bits & (kHiddenBit - 1); + + const bool is_denormal = (E == 0); + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F + kHiddenBit, static_cast(E) - kBias); + + // Compute the boundaries m- and m+ of the floating-point value + // v = f * 2^e. + // + // Determine v- and v+, the floating-point predecessor and successor if v, + // respectively. + // + // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) + // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) + // + // v+ = v + 2^e + // + // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ + // between m- and m+ round to v, regardless of how the input rounding + // algorithm breaks ties. + // + // ---+-------------+-------------+-------------+-------------+--- (A) + // v- m- v m+ v+ + // + // -----------------+------+------+-------------+-------------+--- (B) + // v- m- v m+ v+ + + const bool lower_boundary_is_closer = (F == 0 and E > 1); + const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp(4 * v.f - 1, v.e - 2) // (B) + : diyfp(2 * v.f - 1, v.e - 1); // (A) + + // Determine the normalized w+ = m+. + const diyfp w_plus = diyfp::normalize(m_plus); + + // Determine w- = m- such that e_(w-) = e_(w+). + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// Given normalized diyfp w, Grisu needs to find a (normalized) cached +// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies +// within a certain range [alpha, gamma] (Definition 3.2 from [1]) +// +// alpha <= e = e_c + e_w + q <= gamma +// +// or +// +// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q +// <= f_c * f_w * 2^gamma +// +// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies +// +// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma +// +// or +// +// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) +// +// The choice of (alpha,gamma) determines the size of the table and the form of +// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well +// in practice: +// +// The idea is to cut the number c * w = f * 2^e into two parts, which can be +// processed independently: An integral part p1, and a fractional part p2: +// +// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e +// = (f div 2^-e) + (f mod 2^-e) * 2^e +// = p1 + p2 * 2^e +// +// The conversion of p1 into decimal form requires a series of divisions and +// modulos by (a power of) 10. These operations are faster for 32-bit than for +// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be +// achieved by choosing +// +// -e >= 32 or e <= -32 := gamma +// +// In order to convert the fractional part +// +// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... +// +// into decimal form, the fraction is repeatedly multiplied by 10 and the digits +// d[-i] are extracted in order: +// +// (10 * p2) div 2^-e = d[-1] +// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... +// +// The multiplication by 10 must not overflow. It is sufficient to choose +// +// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. +// +// Since p2 = f mod 2^-e < 2^-e, +// +// -e <= 60 or e >= -60 := alpha + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power // c = f * 2^e ~= 10^k +{ + uint64_t f; + int e; + int k; +}; + +/*! +For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached +power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c +satisfies (Definition 3.2 from [1]) + + alpha <= e_c + e + q <= gamma. +*/ +inline cached_power get_cached_power_for_binary_exponent(int e) +{ + // Now + // + // alpha <= e_c + e + q <= gamma (1) + // ==> f_c * 2^alpha <= c * 2^e * 2^q + // + // and since the c's are normalized, 2^(q-1) <= f_c, + // + // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) + // ==> 2^(alpha - e - 1) <= c + // + // If c were an exakt power of ten, i.e. c = 10^k, one may determine k as + // + // k = ceil( log_10( 2^(alpha - e - 1) ) ) + // = ceil( (alpha - e - 1) * log_10(2) ) + // + // From the paper: + // "In theory the result of the procedure could be wrong since c is rounded, + // and the computation itself is approximated [...]. In practice, however, + // this simple function is sufficient." + // + // For IEEE double precision floating-point numbers converted into + // normalized diyfp's w = f * 2^e, with q = 64, + // + // e >= -1022 (min IEEE exponent) + // -52 (p - 1) + // -52 (p - 1, possibly normalize denormal IEEE numbers) + // -11 (normalize the diyfp) + // = -1137 + // + // and + // + // e <= +1023 (max IEEE exponent) + // -52 (p - 1) + // -11 (normalize the diyfp) + // = 960 + // + // This binary exponent range [-1137,960] results in a decimal exponent + // range [-307,324]. One does not need to store a cached power for each + // k in this range. For each such k it suffices to find a cached power + // such that the exponent of the product lies in [alpha,gamma]. + // This implies that the difference of the decimal exponents of adjacent + // table entries must be less than or equal to + // + // floor( (gamma - alpha) * log_10(2) ) = 8. + // + // (A smaller distance gamma-alpha would require a larger table.) + + // NB: + // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. + + constexpr int kCachedPowersSize = 79; + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr cached_power kCachedPowers[] = + { + { 0xAB70FE17C79AC6CA, -1060, -300 }, + { 0xFF77B1FCBEBCDC4F, -1034, -292 }, + { 0xBE5691EF416BD60C, -1007, -284 }, + { 0x8DD01FAD907FFC3C, -980, -276 }, + { 0xD3515C2831559A83, -954, -268 }, + { 0x9D71AC8FADA6C9B5, -927, -260 }, + { 0xEA9C227723EE8BCB, -901, -252 }, + { 0xAECC49914078536D, -874, -244 }, + { 0x823C12795DB6CE57, -847, -236 }, + { 0xC21094364DFB5637, -821, -228 }, + { 0x9096EA6F3848984F, -794, -220 }, + { 0xD77485CB25823AC7, -768, -212 }, + { 0xA086CFCD97BF97F4, -741, -204 }, + { 0xEF340A98172AACE5, -715, -196 }, + { 0xB23867FB2A35B28E, -688, -188 }, + { 0x84C8D4DFD2C63F3B, -661, -180 }, + { 0xC5DD44271AD3CDBA, -635, -172 }, + { 0x936B9FCEBB25C996, -608, -164 }, + { 0xDBAC6C247D62A584, -582, -156 }, + { 0xA3AB66580D5FDAF6, -555, -148 }, + { 0xF3E2F893DEC3F126, -529, -140 }, + { 0xB5B5ADA8AAFF80B8, -502, -132 }, + { 0x87625F056C7C4A8B, -475, -124 }, + { 0xC9BCFF6034C13053, -449, -116 }, + { 0x964E858C91BA2655, -422, -108 }, + { 0xDFF9772470297EBD, -396, -100 }, + { 0xA6DFBD9FB8E5B88F, -369, -92 }, + { 0xF8A95FCF88747D94, -343, -84 }, + { 0xB94470938FA89BCF, -316, -76 }, + { 0x8A08F0F8BF0F156B, -289, -68 }, + { 0xCDB02555653131B6, -263, -60 }, + { 0x993FE2C6D07B7FAC, -236, -52 }, + { 0xE45C10C42A2B3B06, -210, -44 }, + { 0xAA242499697392D3, -183, -36 }, + { 0xFD87B5F28300CA0E, -157, -28 }, + { 0xBCE5086492111AEB, -130, -20 }, + { 0x8CBCCC096F5088CC, -103, -12 }, + { 0xD1B71758E219652C, -77, -4 }, + { 0x9C40000000000000, -50, 4 }, + { 0xE8D4A51000000000, -24, 12 }, + { 0xAD78EBC5AC620000, 3, 20 }, + { 0x813F3978F8940984, 30, 28 }, + { 0xC097CE7BC90715B3, 56, 36 }, + { 0x8F7E32CE7BEA5C70, 83, 44 }, + { 0xD5D238A4ABE98068, 109, 52 }, + { 0x9F4F2726179A2245, 136, 60 }, + { 0xED63A231D4C4FB27, 162, 68 }, + { 0xB0DE65388CC8ADA8, 189, 76 }, + { 0x83C7088E1AAB65DB, 216, 84 }, + { 0xC45D1DF942711D9A, 242, 92 }, + { 0x924D692CA61BE758, 269, 100 }, + { 0xDA01EE641A708DEA, 295, 108 }, + { 0xA26DA3999AEF774A, 322, 116 }, + { 0xF209787BB47D6B85, 348, 124 }, + { 0xB454E4A179DD1877, 375, 132 }, + { 0x865B86925B9BC5C2, 402, 140 }, + { 0xC83553C5C8965D3D, 428, 148 }, + { 0x952AB45CFA97A0B3, 455, 156 }, + { 0xDE469FBD99A05FE3, 481, 164 }, + { 0xA59BC234DB398C25, 508, 172 }, + { 0xF6C69A72A3989F5C, 534, 180 }, + { 0xB7DCBF5354E9BECE, 561, 188 }, + { 0x88FCF317F22241E2, 588, 196 }, + { 0xCC20CE9BD35C78A5, 614, 204 }, + { 0x98165AF37B2153DF, 641, 212 }, + { 0xE2A0B5DC971F303A, 667, 220 }, + { 0xA8D9D1535CE3B396, 694, 228 }, + { 0xFB9B7CD9A4A7443C, 720, 236 }, + { 0xBB764C4CA7A44410, 747, 244 }, + { 0x8BAB8EEFB6409C1A, 774, 252 }, + { 0xD01FEF10A657842C, 800, 260 }, + { 0x9B10A4E5E9913129, 827, 268 }, + { 0xE7109BFBA19C0C9D, 853, 276 }, + { 0xAC2820D9623BF429, 880, 284 }, + { 0x80444B5E7AA7CF85, 907, 292 }, + { 0xBF21E44003ACDD2D, 933, 300 }, + { 0x8E679C2F5E44FF8F, 960, 308 }, + { 0xD433179D9C8CB841, 986, 316 }, + { 0x9E19DB92B4E31BA9, 1013, 324 }, + }; + + // This computation gives exactly the same results for k as + // k = ceil((kAlpha - e - 1) * 0.30102999566398114) + // for |e| <= 1500, but doesn't require floating-point operations. + // NB: log_10(2) ~= 78913 / 2^18 + assert(e >= -1500); + assert(e <= 1500); + const int f = kAlpha - e - 1; + const int k = (f * 78913) / (1 << 18) + (f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; + assert(index >= 0); + assert(index < kCachedPowersSize); + static_cast(kCachedPowersSize); // Fix warning. + + const cached_power cached = kCachedPowers[index]; + assert(kAlpha <= cached.e + e + 64); + assert(kGamma >= cached.e + e + 64); + + return cached; +} + +/*! +For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. +For n == 0, returns 1 and sets pow10 := 1. +*/ +inline int find_largest_pow10(const uint32_t n, uint32_t& pow10) +{ + // LCOV_EXCL_START + if (n >= 1000000000) + { + pow10 = 1000000000; + return 10; + } + // LCOV_EXCL_STOP + else if (n >= 100000000) + { + pow10 = 100000000; + return 9; + } + else if (n >= 10000000) + { + pow10 = 10000000; + return 8; + } + else if (n >= 1000000) + { + pow10 = 1000000; + return 7; + } + else if (n >= 100000) + { + pow10 = 100000; + return 6; + } + else if (n >= 10000) + { + pow10 = 10000; + return 5; + } + else if (n >= 1000) + { + pow10 = 1000; + return 4; + } + else if (n >= 100) + { + pow10 = 100; + return 3; + } + else if (n >= 10) + { + pow10 = 10; + return 2; + } + else + { + pow10 = 1; + return 1; + } +} + +inline void grisu2_round(char* buf, int len, uint64_t dist, uint64_t delta, + uint64_t rest, uint64_t ten_k) +{ + assert(len >= 1); + assert(dist <= delta); + assert(rest <= delta); + assert(ten_k > 0); + + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // ten_k + // <------> + // <---- rest ----> + // --------------[------------------+----+--------------]-------------- + // w V + // = buf * 10^k + // + // ten_k represents a unit-in-the-last-place in the decimal representation + // stored in buf. + // Decrement buf by ten_k while this takes buf closer to w. + + // The tests are written in this order to avoid overflow in unsigned + // integer arithmetic. + + while (rest < dist + and delta - rest >= ten_k + and (rest + ten_k < dist or dist - rest > rest + ten_k - dist)) + { + assert(buf[len - 1] != '0'); + buf[len - 1]--; + rest += ten_k; + } +} + +/*! +Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. +M- and M+ must be normalized and share the same exponent -60 <= e <= -32. +*/ +inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) +{ + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); + + // Generates the digits (and the exponent) of a decimal floating-point + // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's + // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. + // + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // Grisu2 generates the digits of M+ from left to right and stops as soon as + // V is in [M-,M+]. + + assert(M_plus.e >= kAlpha); + assert(M_plus.e <= kGamma); + + uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) + uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) + + // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): + // + // M+ = f * 2^e + // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e + // = ((p1 ) * 2^-e + (p2 )) * 2^e + // = p1 + p2 * 2^e + + const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e); + + uint32_t p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) + uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e + + // 1) + // + // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] + + assert(p1 > 0); + + uint32_t pow10; + const int k = find_largest_pow10(p1, pow10); + + // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) + // + // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) + // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) + // + // M+ = p1 + p2 * 2^e + // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e + // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e + // = d[k-1] * 10^(k-1) + ( rest) * 2^e + // + // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) + // + // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] + // + // but stop as soon as + // + // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e + + int n = k; + while (n > 0) + { + // Invariants: + // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) + // pow10 = 10^(n-1) <= p1 < 10^n + // + const uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) + const uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) + // + // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e + // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) + // + p1 = r; + n--; + // + // M+ = buffer * 10^n + (p1 + p2 * 2^e) + // pow10 = 10^n + // + + // Now check if enough digits have been generated. + // Compute + // + // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e + // + // Note: + // Since rest and delta share the same exponent e, it suffices to + // compare the significands. + const uint64_t rest = (uint64_t{p1} << -one.e) + p2; + if (rest <= delta) + { + // V = buffer * 10^n, with M- <= V <= M+. + + decimal_exponent += n; + + // We may now just stop. But instead look if the buffer could be + // decremented to bring V closer to w. + // + // pow10 = 10^n is now 1 ulp in the decimal representation V. + // The rounding procedure works with diyfp's with an implicit + // exponent of e. + // + // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e + // + const uint64_t ten_n = uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + // pow10 = 10^(n-1) <= p1 < 10^n + // Invariants restored. + } + + // 2) + // + // The digits of the integral part have been generated: + // + // M+ = d[k-1]...d[1]d[0] + p2 * 2^e + // = buffer + p2 * 2^e + // + // Now generate the digits of the fractional part p2 * 2^e. + // + // Note: + // No decimal point is generated: the exponent is adjusted instead. + // + // p2 actually represents the fraction + // + // p2 * 2^e + // = p2 / 2^-e + // = d[-1] / 10^1 + d[-2] / 10^2 + ... + // + // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) + // + // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m + // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) + // + // using + // + // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) + // = ( d) * 2^-e + ( r) + // + // or + // 10^m * p2 * 2^e = d + r * 2^e + // + // i.e. + // + // M+ = buffer + p2 * 2^e + // = buffer + 10^-m * (d + r * 2^e) + // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e + // + // and stop as soon as 10^-m * r * 2^e <= delta * 2^e + + assert(p2 > delta); + + int m = 0; + for (;;) + { + // Invariant: + // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e + // = buffer * 10^-m + 10^-m * (p2 ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e + // + assert(p2 <= UINT64_MAX / 10); + p2 *= 10; + const uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e + const uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e + // + // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) + // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + p2 = r; + m++; + // + // M+ = buffer * 10^-m + 10^-m * p2 * 2^e + // Invariant restored. + + // Check if enough digits have been generated. + // + // 10^-m * p2 * 2^e <= delta * 2^e + // p2 * 2^e <= 10^m * delta * 2^e + // p2 <= 10^m * delta + delta *= 10; + dist *= 10; + if (p2 <= delta) + { + break; + } + } + + // V = buffer * 10^-m, with M- <= V <= M+. + + decimal_exponent -= m; + + // 1 ulp in the decimal representation is now 10^-m. + // Since delta and dist are now scaled by 10^m, we need to do the + // same with ulp in order to keep the units in sync. + // + // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e + // + const uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // By construction this algorithm generates the shortest possible decimal + // number (Loitsch, Theorem 6.2) which rounds back to w. + // For an input number of precision p, at least + // + // N = 1 + ceil(p * log_10(2)) + // + // decimal digits are sufficient to identify all binary floating-point + // numbers (Matula, "In-and-Out conversions"). + // This implies that the algorithm does not produce more than N decimal + // digits. + // + // N = 17 for p = 53 (IEEE double precision) + // N = 9 for p = 24 (IEEE single precision) +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +inline void grisu2(char* buf, int& len, int& decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) +{ + assert(m_plus.e == m_minus.e); + assert(m_plus.e == v.e); + + // --------(-----------------------+-----------------------)-------- (A) + // m- v m+ + // + // --------------------(-----------+-----------------------)-------- (B) + // m- v m+ + // + // First scale v (and m- and m+) such that the exponent is in the range + // [alpha, gamma]. + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k + + // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + + // ----(---+---)---------------(---+---)---------------(---+---)---- + // w- w w+ + // = c*m- = c*v = c*m+ + // + // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and + // w+ are now off by a small amount. + // In fact: + // + // w - v * 10^k < 1 ulp + // + // To account for this inaccuracy, add resp. subtract 1 ulp. + // + // --------+---[---------------(---+---)---------------]---+-------- + // w- M- w M+ w+ + // + // Now any number in [M-, M+] (bounds included) will round to w when input, + // regardless of how the input rounding algorithm breaks ties. + // + // And digit_gen generates the shortest possible such number in [M-, M+]. + // Note that this does not mean that Grisu2 always generates the shortest + // possible number in the interval (m-, m+). + const diyfp M_minus(w_minus.f + 1, w_minus.e); + const diyfp M_plus (w_plus.f - 1, w_plus.e ); + + decimal_exponent = -cached.k; // = -(-k) = k + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +template +void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) +{ + static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, + "internal error: not enough precision"); + + assert(std::isfinite(value)); + assert(value > 0); + + // If the neighbors (and boundaries) of 'value' are always computed for double-precision + // numbers, all float's can be recovered using strtod (and strtof). However, the resulting + // decimal representations are not exactly "short". + // + // The documentation for 'std::to_chars' (http://en.cppreference.com/w/cpp/utility/to_chars) + // says "value is converted to a string as if by std::sprintf in the default ("C") locale" + // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' + // does. + // On the other hand, the documentation for 'std::to_chars' requires that "parsing the + // representation using the corresponding std::from_chars function recovers value exactly". That + // indicates that single precision floating-point numbers should be recovered using + // 'std::strtof'. + // + // NB: If the neighbors are computed for single-precision numbers, there is a single float + // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision + // value is off by 1 ulp. +#if 0 + const boundaries w = compute_boundaries(static_cast(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +/*! +@brief appends a decimal representation of e to buf +@return a pointer to the element following the exponent. +@pre -1000 < e < 1000 +*/ +inline char* append_exponent(char* buf, int e) +{ + assert(e > -1000); + assert(e < 1000); + + if (e < 0) + { + e = -e; + *buf++ = '-'; + } + else + { + *buf++ = '+'; + } + + uint32_t k = static_cast(e); + if (k < 10) + { + // Always print at least two digits in the exponent. + // This is for compatibility with printf("%g"). + *buf++ = '0'; + *buf++ = static_cast('0' + k); + } + else if (k < 100) + { + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + else + { + *buf++ = static_cast('0' + k / 100); + k %= 100; + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + + return buf; +} + +/*! +@brief prettify v = buf * 10^decimal_exponent + +If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point +notation. Otherwise it will be printed in exponential notation. + +@pre min_exp < 0 +@pre max_exp > 0 +*/ +inline char* format_buffer(char* buf, int len, int decimal_exponent, + int min_exp, int max_exp) +{ + assert(min_exp < 0); + assert(max_exp > 0); + + const int k = len; + const int n = len + decimal_exponent; + + // v = buf * 10^(n-k) + // k is the length of the buffer (number of decimal digits) + // n is the position of the decimal point relative to the start of the buffer. + + if (k <= n and n <= max_exp) + { + // digits[000] + // len <= max_exp + 2 + + std::memset(buf + k, '0', static_cast(n - k)); + // Make it look like a floating-point number (#362, #378) + buf[n + 0] = '.'; + buf[n + 1] = '0'; + return buf + (n + 2); + } + + if (0 < n and n <= max_exp) + { + // dig.its + // len <= max_digits10 + 1 + + assert(k > n); + + std::memmove(buf + (n + 1), buf + n, static_cast(k - n)); + buf[n] = '.'; + return buf + (k + 1); + } + + if (min_exp < n and n <= 0) + { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + -n), buf, static_cast(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast(-n)); + return buf + (2 + (-n) + k); + } + + if (k == 1) + { + // dE+123 + // len <= 1 + 5 + + buf += 1; + } + else + { + // d.igitsE+123 + // len <= max_digits10 + 1 + 5 + + std::memmove(buf + 2, buf + 1, static_cast(k - 1)); + buf[1] = '.'; + buf += 1 + k; + } + + *buf++ = 'e'; + return append_exponent(buf, n - 1); +} + +} // namespace dtoa_impl + +/*! +@brief generates a decimal representation of the floating-point number value in [first, last). + +The format of the resulting decimal representation is similar to printf's %g +format. Returns an iterator pointing past-the-end of the decimal representation. + +@note The input number must be finite, i.e. NaN's and Inf's are not supported. +@note The buffer must be large enough. +@note The result is NOT null-terminated. +*/ +template +char* to_chars(char* first, char* last, FloatType value) +{ + static_cast(last); // maybe unused - fix warning + assert(std::isfinite(value)); + + // Use signbit(value) instead of (value < 0) since signbit works for -0. + if (std::signbit(value)) + { + value = -value; + *first++ = '-'; + } + + if (value == 0) // +-0 + { + *first++ = '0'; + // Make it look like a floating-point number (#362, #378) + *first++ = '.'; + *first++ = '0'; + return first; + } + + assert(last - first >= std::numeric_limits::max_digits10); + + // Compute v = buffer * 10^decimal_exponent. + // The decimal digits are stored in the buffer, which needs to be interpreted + // as an unsigned decimal integer. + // len is the length of the buffer, i.e. the number of decimal digits. + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + + assert(len <= std::numeric_limits::max_digits10); + + // Format the buffer like printf("%.*g", prec, value) + constexpr int kMinExp = -4; + // Use digits10 here to increase compatibility with version 2. + constexpr int kMaxExp = std::numeric_limits::digits10; + + assert(last - first >= kMaxExp + 2); + assert(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); + assert(last - first >= std::numeric_limits::max_digits10 + 6); + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} + +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// serialization // +/////////////////// + +template +class serializer +{ + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + */ + serializer(output_adapter_t s, const char ichar) + : o(std::move(s)), loc(std::localeconv()), + thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), + decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)), + indent_char(ichar), indent_string(512, indent_char) + {} + + // delete because of pointer members + serializer(const serializer&) = delete; + serializer& operator=(const serializer&) = delete; + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(const BasicJsonType& val, const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) + { + switch (val.m_type) + { + case value_t::object: + { + if (val.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + + o->write_character('}'); + } + + return; + } + + case value_t::array: + { + if (val.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (pretty_print) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + + o->write_character(']'); + } + + return; + } + + case value_t::string: + { + o->write_character('\"'); + dump_escaped(*val.m_value.string, ensure_ascii); + o->write_character('\"'); + return; + } + + case value_t::boolean: + { + if (val.m_value.boolean) + { + o->write_characters("true", 4); + } + else + { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: + { + dump_integer(val.m_value.number_integer); + return; + } + + case value_t::number_unsigned: + { + dump_integer(val.m_value.number_unsigned); + return; + } + + case value_t::number_float: + { + dump_float(val.m_value.number_float); + return; + } + + case value_t::discarded: + { + o->write_characters("", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + } + } + + private: + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(const string_t& s, const bool ensure_ascii) + { + uint32_t codepoint; + uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer + + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) + { + case UTF8_ACCEPT: // decode found a new code point + { + switch (codepoint) + { + case 0x08: // backspace + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } + + case 0x09: // horizontal tab + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast(codepoint)); + bytes += 6; + } + else + { + std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast(0xD7C0 + (codepoint >> 10)), + static_cast(0xDC00 + (codepoint & 0x3FF))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + ss.str())); + } + + default: // decode found yet incomplete multi-byte code point + { + if (not ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; + } + break; + } + } + } + + if (JSON_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o->write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(static_cast(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + ss.str())); + } + } + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template::value or + std::is_same::value, + int> = 0> + void dump_integer(NumberType x) + { + // special case for "0" + if (x == 0) + { + o->write_character('0'); + return; + } + + const bool is_negative = (x <= 0) and (x != 0); // see issue #755 + std::size_t i = 0; + + while (x != 0) + { + // spare 1 byte for '\0' + assert(i < number_buffer.size() - 1); + + const auto digit = std::labs(static_cast(x % 10)); + number_buffer[i++] = static_cast('0' + digit); + x /= 10; + } + + if (is_negative) + { + // make sure there is capacity for the '-' + assert(i < number_buffer.size() - 2); + number_buffer[i++] = '-'; + } + + std::reverse(number_buffer.begin(), number_buffer.begin() + i); + o->write_characters(number_buffer.data(), i); + } + + /*! + @brief dump a floating-point number + + Dump a given floating-point number to output stream @a o. Works internally + with @a number_buffer. + + @param[in] x floating-point number to dump + */ + void dump_float(number_float_t x) + { + // NaN / inf + if (not std::isfinite(x)) + { + o->write_characters("null", 4); + return; + } + + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if == . + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 24 and std::numeric_limits::max_exponent == 128) or + (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 53 and std::numeric_limits::max_exponent == 1024); + + dump_float(x, std::integral_constant()); + } + + void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + char* begin = number_buffer.data(); + char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o->write_characters(begin, static_cast(end - begin)); + } + + void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits::max_digits10; + + // the actual conversion + std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + // negative value indicates an error + assert(len > 0); + // check if buffer was large enough + assert(static_cast(len) < number_buffer.size()); + + // erase thousands separator + if (thousands_sep != '\0') + { + const auto end = std::remove(number_buffer.begin(), + number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + assert((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } + + // convert decimal point to '.' + if (decimal_point != '\0' and decimal_point != '.') + { + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if (dec_pos != number_buffer.end()) + { + *dec_pos = '.'; + } + } + + o->write_characters(number_buffer.data(), static_cast(len)); + + // determine if need to append ".0" + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) + { + return (c == '.' or c == 'e'); + }); + + if (value_is_int_like) + { + o->write_characters(".0", 2); + } + } + + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept + { + static const std::array utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 + } + }; + + const uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6) + : static_cast(0xff >> type) & (byte); + + state = utf8d[256u + state * 16u + type]; + return state; + } + + private: + /// the output of the serializer + output_adapter_t o = nullptr; + + /// a (hopefully) large enough character buffer + std::array number_buffer{{}}; + + /// the locale + const std::lconv* loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; + + /// string buffer + std::array string_buffer{{}}; + + /// the indentation character + const char indent_char; + /// the indentation string + string_t indent_string; +}; +} +} + +// #include + + +#include +#include + +namespace nlohmann +{ +namespace detail +{ +template +class json_ref +{ + public: + using value_type = BasicJsonType; + + json_ref(value_type&& value) + : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true) + {} + + json_ref(const value_type& value) + : value_ref(const_cast(&value)), is_rvalue(false) + {} + + json_ref(std::initializer_list init) + : owned_value(init), value_ref(&owned_value), is_rvalue(true) + {} + + template + json_ref(Args&& ... args) + : owned_value(std::forward(args)...), value_ref(&owned_value), is_rvalue(true) + {} + + // class should be movable only + json_ref(json_ref&&) = default; + json_ref(const json_ref&) = delete; + json_ref& operator=(const json_ref&) = delete; + + value_type moved_or_copied() const + { + if (is_rvalue) + { + return std::move(*value_ref); + } + return *value_ref; + } + + value_type const& operator*() const + { + return *static_cast(value_ref); + } + + value_type const* operator->() const + { + return static_cast(value_ref); + } + + private: + mutable value_type owned_value = nullptr; + value_type* value_ref = nullptr; + const bool is_rvalue; +}; +} +} + +// #include + + +#include // assert +#include // accumulate +#include // string +#include // vector + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +template +class json_pointer +{ + // allow basic_json to access private members + NLOHMANN_BASIC_JSON_TPL_DECLARATION + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the empty + string is assumed which references the whole JSON value + + @throw parse_error.107 if the given JSON pointer @a s is nonempty and does + not begin with a slash (`/`); see example below + + @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is + not followed by `0` (representing `~`) or `1` (representing `/`); see + example below + + @liveexample{The example shows the construction several valid JSON pointers + as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`., + json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const noexcept + { + return std::accumulate(reference_tokens.begin(), reference_tokens.end(), + std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + escape(b); + }); + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + /*! + @param[in] s reference token to be converted into an array index + + @return integer representation of @a s + + @throw out_of_range.404 if string @a s could not be converted to an integer + */ + static int array_index(const std::string& s) + { + std::size_t processed_chars = 0; + const int res = std::stoi(s, &processed_chars); + + // check if the string was completely read + if (JSON_UNLIKELY(processed_chars != s.size())) + { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); + } + + return res; + } + + private: + /*! + @brief remove and return last reference pointer + @throw out_of_range.405 if JSON pointer has no parent + */ + std::string pop_back() + { + if (JSON_UNLIKELY(is_root())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; + } + + /// return whether pointer points to the root document + bool is_root() const + { + return reference_tokens.empty(); + } + + json_pointer top() const + { + if (JSON_UNLIKELY(is_root())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + + @throw parse_error.109 if array index is not a number + @throw type_error.313 if value cannot be unflattened + */ + BasicJsonType& get_and_create(BasicJsonType& j) const + { + using size_type = typename BasicJsonType::size_type; + auto result = &j; + + // in case no reference tokens exist, return a reference to the JSON value + // j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case detail::value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + JSON_TRY + { + result = &result->operator[](static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + single value; that is, with an empty list of reference tokens. + */ + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries to + create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_unchecked(BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->m_type == detail::value_t::null) + { + // check if reference token is a number + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const char x) + { + return (x >= '0' and x <= '9'); + }); + + // change value to array for numbers or "-" or to object otherwise + *ptr = (nums or reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; + } + + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + if (reference_token == "-") + { + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_checked(BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" cannot be used for const access + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // use unchecked array access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_checked(const BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @brief split the string input to reference tokens + + @note This function is only called by the json_pointer constructor. + All exceptions below are documented there. + + @throw parse_error.107 if the pointer is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' + */ + static std::vector split(const std::string& reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (JSON_UNLIKELY(reference_string[0] != '/')) + { + JSON_THROW(detail::parse_error::create(107, 1, + "JSON pointer must be empty or begin with '/' - was: '" + + reference_string + "'")); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + std::size_t slash = reference_string.find_first_of('/', 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == string::npos+1 = 0 + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = slash + 1, + // find next slash + slash = reference_string.find_first_of('/', start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (std::size_t pos = reference_token.find_first_of('~'); + pos != std::string::npos; + pos = reference_token.find_first_of('~', pos + 1)) + { + assert(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (JSON_UNLIKELY(pos == reference_token.size() - 1 or + (reference_token[pos + 1] != '0' and + reference_token[pos + 1] != '1'))) + { + JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); + } + } + + // finally, store the reference token + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + /*! + @brief replace all occurrences of a substring by another string + + @param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t + @param[in] f the substring to replace with @a t + @param[in] t the string to replace @a f + + @pre The search string @a f must not be empty. **This precondition is + enforced with an assertion.** + + @since version 2.0.0 + */ + static void replace_substring(std::string& s, const std::string& f, + const std::string& t) + { + assert(not f.empty()); + 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 + {} + } + + /// escape "~"" to "~0" and "/" to "~1" + static std::string escape(std::string s) + { + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + /// unescape "~1" to tilde and "~0" to slash (order is important!) + static void unescape(std::string& s) + { + replace_substring(s, "~1", "/"); + replace_substring(s, "~0", "~"); + } + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const std::string& reference_string, + const BasicJsonType& value, + BasicJsonType& result) + { + switch (value.m_type) + { + case detail::value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (std::size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case detail::value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + + @throw parse_error.109 if array index is not a number + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + @throw type_error.313 if value cannot be unflattened + */ + static BasicJsonType + unflatten(const BasicJsonType& value) + { + if (JSON_UNLIKELY(not value.is_object())) + { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); + } + + BasicJsonType result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (JSON_UNLIKELY(not element.second.is_primitive())) + { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); + } + + // assign value to reference pointed to by JSON pointer; Note that if + // the JSON pointer is "" (i.e., points to the whole value), function + // get_and_create returns a reference to result itself. An assignment + // will then create a primitive value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + friend bool operator==(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return (lhs.reference_tokens == rhs.reference_tokens); + } + + friend bool operator!=(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return not (lhs == rhs); + } + + /// the reference tokens + std::vector reference_tokens; +}; +} + +// #include + + +#include + +// #include + +// #include + + +namespace nlohmann +{ +template +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @param[in] j JSON value to read from + @param[in,out] val value to write to + */ + template + static void from_json(BasicJsonType&& j, ValueType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward(j), val))) + { + ::nlohmann::from_json(std::forward(j), val); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref basic_json + class. + + @param[in,out] j JSON value to write to + @param[in] val value to read from + */ + template + static void to_json(BasicJsonType& j, ValueType&& val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward(val)))) + { + ::nlohmann::to_json(j, std::forward(val)); + } +}; +} + + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ + +/*! +@brief a class to store JSON values + +@tparam ObjectType type for JSON objects (`std::map` by default; will be used +in @ref object_t) +@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used +in @ref array_t) +@tparam StringType type for JSON strings and object keys (`std::string` by +default; will be used in @ref string_t) +@tparam BooleanType type for JSON booleans (`bool` by default; will be used +in @ref boolean_t) +@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by +default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) +@tparam NumberFloatType type for JSON floating-point numbers (`double` by +default; will be used in @ref number_float_t) +@tparam AllocatorType type of the allocator to use (`std::allocator` by +default) +@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` +and `from_json()` (@ref adl_serializer by default) + +@requirement The class satisfies the following concept requirements: +- Basic + - [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible): + JSON values can be default constructed. The result will be a JSON null + value. + - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): + A JSON value can be constructed from an rvalue argument. + - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): + A JSON value can be copy-constructed from an lvalue expression. + - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): + A JSON value van be assigned from an rvalue argument. + - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): + A JSON value can be copy-assigned from an lvalue expression. + - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible): + JSON values can be destructed. +- Layout + - [StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType): + JSON values have + [standard layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout): + All non-static data members are private and standard layout types, the + class has no virtual functions or (virtual) base classes. +- Library-wide + - [EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable): + JSON values can be compared with `==`, see @ref + operator==(const_reference,const_reference). + - [LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable): + JSON values can be compared with `<`, see @ref + operator<(const_reference,const_reference). + - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable): + Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of + other compatible types, using unqualified function call @ref swap(). + - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer): + JSON values can be compared against `std::nullptr_t` objects which are used + to model the `null` value. +- Container + - [Container](http://en.cppreference.com/w/cpp/concept/Container): + JSON values can be used like STL containers and provide iterator access. + - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer); + JSON values can be used like STL containers and provide reverse iterator + access. + +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@internal +@note ObjectType trick from http://stackoverflow.com/a/9860911 +@endinternal + +@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange +Format](http://rfc7159.net/rfc7159) + +@since version 1.0.0 + +@nosubgrouping +*/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +class basic_json +{ + private: + template friend struct detail::external_constructor; + friend ::nlohmann::json_pointer; + friend ::nlohmann::detail::parser; + friend ::nlohmann::detail::serializer; + template + friend class ::nlohmann::detail::iter_impl; + template + friend class ::nlohmann::detail::binary_writer; + template + friend class ::nlohmann::detail::binary_reader; + + /// workaround type for MSVC + using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + + // convenience aliases for types residing in namespace detail; + using lexer = ::nlohmann::detail::lexer; + using parser = ::nlohmann::detail::parser; + + using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; + template + using internal_iterator = ::nlohmann::detail::internal_iterator; + template + using iter_impl = ::nlohmann::detail::iter_impl; + template + using iteration_proxy = ::nlohmann::detail::iteration_proxy; + template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; + + template + using output_adapter_t = ::nlohmann::detail::output_adapter_t; + + using binary_reader = ::nlohmann::detail::binary_reader; + template using binary_writer = ::nlohmann::detail::binary_writer; + + using serializer = ::nlohmann::detail::serializer; + + public: + using value_t = detail::value_t; + /// @copydoc nlohmann::json_pointer + using json_pointer = ::nlohmann::json_pointer; + template + using json_serializer = JSONSerializer; + /// helper type for initializer lists of basic_json values + using initializer_list_t = std::initializer_list>; + + //////////////// + // exceptions // + //////////////// + + /// @name exceptions + /// Classes to implement user-defined exceptions. + /// @{ + + /// @copydoc detail::exception + using exception = detail::exception; + /// @copydoc detail::parse_error + using parse_error = detail::parse_error; + /// @copydoc detail::invalid_iterator + using invalid_iterator = detail::invalid_iterator; + /// @copydoc detail::type_error + using type_error = detail::type_error; + /// @copydoc detail::out_of_range + using out_of_range = detail::out_of_range; + /// @copydoc detail::other_error + using other_error = detail::other_error; + + /// @} + + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType; + + /// the type of an element pointer + using pointer = typename std::allocator_traits::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits::const_pointer; + + /// an iterator for a basic_json container + using iterator = iter_impl; + /// a const iterator for a basic_json container + using const_iterator = iter_impl; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator; + + /// @} + + + /*! + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() + { + return allocator_type(); + } + + /*! + @brief returns version information on the library + + This function returns a JSON object with information about the library, + including the version number and information on the platform and compiler. + + @return JSON object holding version information + key | description + ----------- | --------------- + `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). + `copyright` | The copyright line for the library as string. + `name` | The name of the library as string. + `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. + `url` | The URL of the project as string. + `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string). + + @liveexample{The following code shows an example output of the `meta()` + function.,meta} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @complexity Constant. + + @since 2.1.0 + */ + static basic_json meta() + { + basic_json result; + + result["copyright"] = "(C) 2013-2017 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["url"] = "https://github.com/nlohmann/json"; + result["version"]["string"] = + std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_PATCH); + result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; + result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; + result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; + +#ifdef _WIN32 + result["platform"] = "win32"; +#elif defined __linux__ + result["platform"] = "linux"; +#elif defined __APPLE__ + result["platform"] = "apple"; +#elif defined __unix__ + result["platform"] = "unix"; +#else + result["platform"] = "unknown"; +#endif + +#if defined(__ICC) || defined(__INTEL_COMPILER) + result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; +#elif defined(__clang__) + result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; +#elif defined(__GNUC__) || defined(__GNUG__) + result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; +#elif defined(__HP_cc) || defined(__HP_aCC) + result["compiler"] = "hp" +#elif defined(__IBMCPP__) + result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; +#elif defined(_MSC_VER) + result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; +#elif defined(__PGI) + result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; +#elif defined(__SUNPRO_CC) + result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; +#else + result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; +#endif + +#ifdef __cplusplus + result["compiler"]["c++"] = std::to_string(__cplusplus); +#else + result["compiler"]["c++"] = "unknown"; +#endif + return result; + } + + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + +#if defined(JSON_HAS_CPP_14) + // Use transparent comparator if possible, combined with perfect forwarding + // on find() and count() calls prevents unnecessary string construction. + using object_comparator_t = std::less<>; +#else + using object_comparator_t = std::less; +#endif + + /*! + @brief a type for an object + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: + > An object is an unordered collection of zero or more name/value pairs, + > where a name is a string and a value is a string, number, boolean, null, + > object, or array. + + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) + + #### Default type + + With the default values for @a ObjectType (`std::map`), @a StringType + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: + + @code {.cpp} + std::map< + std::string, // key_type + basic_json, // value_type + std::less, // key_compare + std::allocator> // allocator_type + > + @endcode + + #### Behavior + + The choice of @a object_t influences the behavior of the JSON class. With + the default type, objects have the following behavior: + + - When all names are unique, objects will be interoperable in the sense + that all software implementations receiving that object will agree on + the name-value mappings. + - When the names within an object are not unique, it is unspecified which + one of the values for a given key will be chosen. For instance, + `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or + `{"key": 2}`. + - Internally, name/value pairs are stored in lexicographical order of the + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. + - When comparing objects, the order of the name/value pairs is irrelevant. + This makes objects interoperable in the sense that they will not be + affected by these differences. For instance, `{"b": 1, "a": 2}` and + `{"a": 2, "b": 1}` will be treated as equal. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the object's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. + + #### Storage + + Objects are stored as pointers in a @ref basic_json type. That is, for any + access to object values, a pointer of type `object_t*` must be + dereferenced. + + @sa @ref array_t -- type for an array value + + @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. + */ + using object_t = ObjectType>>; + + /*! + @brief a type for an array + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: + > An array is an ordered sequence of zero or more values. + + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) + + #### Default type + + With the default values for @a ArrayType (`std::vector`) and @a + AllocatorType (`std::allocator`), the default value for @a array_t is: + + @code {.cpp} + std::vector< + basic_json, // value_type + std::allocator // allocator_type + > + @endcode + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the array's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. + + #### Storage + + Arrays are stored as pointers in a @ref basic_json type. That is, for any + access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa @ref object_t -- type for an object value + + @since version 1.0.0 + */ + using array_t = ArrayType>; + + /*! + @brief a type for a string + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: + > A string is a sequence of zero or more Unicode characters. + + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. + + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. + + #### Default type + + With the default values for @a StringType (`std::string`), the default + value for @a string_t is: + + @code {.cpp} + std::string + @endcode + + #### Encoding + + Strings are stored in UTF-8 encoding. Therefore, functions like + `std::string::size()` or `std::string::length()` return the number of + bytes in the string rather than the number of characters or glyphs. + + #### String comparison + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > Software implementations are typically required to test names of object + > members for equality. Implementations that transform the textual + > representation into sequences of Unicode code units and then perform the + > comparison numerically, code unit by code unit, are interoperable in the + > sense that implementations will agree in all cases on equality or + > inequality of two strings. For example, implementations that compare + > strings with escaped characters unconverted may incorrectly find that + > `"a\\b"` and `"a\u005Cb"` are not equal. + + This implementation is interoperable as it does compare strings code unit + by code unit. + + #### Storage + + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be + dereferenced. + + @since version 1.0.0 + */ + using string_t = StringType; + + /*! + @brief a type for a boolean + + [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a + type which differentiates the two literals `true` and `false`. + + To store objects in C++, a type is defined by the template parameter @a + BooleanType which chooses the type to use. + + #### Default type + + With the default values for @a BooleanType (`bool`), the default value for + @a boolean_t is: + + @code {.cpp} + bool + @endcode + + #### Storage + + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 + */ + using boolean_t = BooleanType; + + /*! + @brief a type for a number (integer) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store integer numbers in C++, a type is defined by the template + parameter @a NumberIntegerType which chooses the type to use. + + #### Default type + + With the default values for @a NumberIntegerType (`int64_t`), the default + value for @a number_integer_t is: + + @code {.cpp} + int64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `9223372036854775807` (INT64_MAX) and the minimal integer number + that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange of the exactly supported range [INT64_MIN, + INT64_MAX], this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_integer_t = NumberIntegerType; + + /*! + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], + this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + + /*! + @brief a type for a number (floating-point) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store floating-point numbers in C++, a type is defined by the template + parameter @a NumberFloatType which chooses the type to use. + + #### Default type + + With the default values for @a NumberFloatType (`double`), the default + value for @a number_float_t is: + + @code {.cpp} + double + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ + floating-point literal `01.2` will be serialized to `1.2`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > This specification allows implementations to set limits on the range and + > precision of numbers accepted. Since software that implements IEEE + > 754-2008 binary64 (double precision) numbers is generally available and + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected + > precision. + + This implementation does exactly follow this approach, as it uses double + precision floating-point numbers. Note values smaller than + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` + will be stored as NaN internally and be serialized to `null`. + + #### Storage + + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa @ref number_integer_t -- type for number values (integer) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_float_t = NumberFloatType; + + /// @} + + private: + + /// helper for exception-safe object creation + template + static T* create(Args&& ... args) + { + AllocatorType alloc; + using AllocatorTraits = std::allocator_traits>; + + auto deleter = [&](T * object) + { + AllocatorTraits::deallocate(alloc, object, 1); + }; + std::unique_ptr object(AllocatorTraits::allocate(alloc, 1), deleter); + AllocatorTraits::construct(alloc, object.get(), std::forward(args)...); + assert(object != nullptr); + return object.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create(); + break; + } + + case value_t::array: + { + array = create(); + break; + } + + case value_t::string: + { + string = create(""); + break; + } + + case value_t::boolean: + { + boolean = boolean_t(false); + break; + } + + case value_t::number_integer: + { + number_integer = number_integer_t(0); + break; + } + + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } + + case value_t::number_float: + { + number_float = number_float_t(0.0); + break; + } + + case value_t::null: + { + object = nullptr; // silence warning, see #821 + break; + } + + default: + { + object = nullptr; // silence warning, see #821 + if (JSON_UNLIKELY(t == value_t::null)) + { + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.1.1")); // LCOV_EXCL_LINE + } + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) + { + string = create(value); + } + + /// constructor for rvalue strings + json_value(string_t&& value) + { + string = create(std::move(value)); + } + + /// constructor for objects + json_value(const object_t& value) + { + object = create(value); + } + + /// constructor for rvalue objects + json_value(object_t&& value) + { + object = create(std::move(value)); + } + + /// constructor for arrays + json_value(const array_t& value) + { + array = create(value); + } + + /// constructor for rvalue arrays + json_value(array_t&& value) + { + array = create(std::move(value)); + } + + void destroy(value_t t) noexcept + { + switch (t) + { + case value_t::object: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, object); + std::allocator_traits::deallocate(alloc, object, 1); + break; + } + + case value_t::array: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, array); + std::allocator_traits::deallocate(alloc, array, 1); + break; + } + + case value_t::string: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, string); + std::allocator_traits::deallocate(alloc, string, 1); + break; + } + + default: + { + break; + } + } + } + }; + + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + */ + void assert_invariant() const noexcept + { + assert(m_type != value_t::object or m_value.object != nullptr); + assert(m_type != value_t::array or m_value.array != nullptr); + assert(m_type != value_t::string or m_value.string != nullptr); + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /*! + @brief parser event types + + The parser callback distinguishes the following events: + - `object_start`: the parser read `{` and started to process a JSON object + - `key`: the parser read a key of a value in an object + - `object_end`: the parser read `}` and finished processing a JSON object + - `array_start`: the parser read `[` and started to process a JSON array + - `array_end`: the parser read `]` and finished processing a JSON array + - `value`: the parser finished reading a JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + @sa @ref parser_callback_t for more information and examples + */ + using parse_event_t = typename parser::parse_event_t; + + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse, it is called on certain events + (passed as @ref parse_event_t via parameter @a event) with a set recursion + depth @a depth and context JSON value @a parsed. The return value of the + callback function is a boolean indicating whether the element that emitted + the callback shall be kept or not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa @ref parse for examples + + @since version 1.0.0 + */ + using parser_callback_t = typename parser::parser_callback_t; + + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /*! + @brief create an empty value with a given type + + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @param[in] v the type of the value to create + + @complexity Constant. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @sa @ref clear() -- restores the postcondition of this constructor + + @since version 1.0.0 + */ + basic_json(const value_t v) + : m_type(v), m_value(v) + { + assert_invariant(); + } + + /*! + @brief create a null object + + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} + + @since version 1.0.0 + */ + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) + { + assert_invariant(); + } + + /*! + @brief create a JSON value + + This is a "catch all" constructor for all compatible JSON types; that is, + types for which a `to_json()` method exists. The constructor forwards the + parameter @a val to that method (to `json_serializer::to_json` method + with `U = uncvref_t`, to be exact). + + Template type @a CompatibleType includes, but is not limited to, the + following types: + - **arrays**: @ref array_t and all kinds of compatible containers such as + `std::vector`, `std::deque`, `std::list`, `std::forward_list`, + `std::array`, `std::valarray`, `std::set`, `std::unordered_set`, + `std::multiset`, and `std::unordered_multiset` with a `value_type` from + which a @ref basic_json value can be constructed. + - **objects**: @ref object_t and all kinds of compatible associative + containers such as `std::map`, `std::unordered_map`, `std::multimap`, + and `std::unordered_multimap` with a `key_type` compatible to + @ref string_t and a `value_type` from which a @ref basic_json value can + be constructed. + - **strings**: @ref string_t, string literals, and all compatible string + containers can be used. + - **numbers**: @ref number_integer_t, @ref number_unsigned_t, + @ref number_float_t, and all convertible number types such as `int`, + `size_t`, `int64_t`, `float` or `double` can be used. + - **boolean**: @ref boolean_t / `bool` can be used. + + See the examples below. + + @tparam CompatibleType a type such that: + - @a CompatibleType is not derived from `std::istream`, + - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move + constructors), + - @a CompatibleType is not a @ref basic_json nested type (e.g., + @ref json_pointer, @ref iterator, etc ...) + - @ref @ref json_serializer has a + `to_json(basic_json_t&, CompatibleType&&)` method + + @tparam U = `uncvref_t` + + @param[in] val the value to be forwarded to the respective constructor + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @liveexample{The following code shows the constructor with several + compatible types.,basic_json__CompatibleType} + + @since version 2.1.0 + */ + template , + detail::enable_if_t< + detail::is_compatible_type::value, int> = 0> + basic_json(CompatibleType && val) noexcept(noexcept( + JSONSerializer::to_json(std::declval(), + std::forward(val)))) + { + JSONSerializer::to_json(*this, std::forward(val)); + assert_invariant(); + } + + /*! + @brief create a container (array or object) from an initializer list + + Creates a JSON value of type array or object from the passed initializer + list @a init. In case @a type_deduction is `true` (default), the type of + the JSON value to be created is deducted from the initializer list @a init + according to the following rules: + + 1. If the list is empty, an empty JSON object value `{}` is created. + 2. If the list consists of pairs whose first element is a string, a JSON + object value is created where the first elements of the pairs are + treated as keys and the second elements are as values. + 3. In all other cases, an array is created. + + The rules aim to create the best fit between a C++ initializer list and + JSON values. The rationale is as follows: + + 1. The empty initializer list is written as `{}` which is exactly an empty + JSON object. + 2. C++ has no way of describing mapped types other than to list a list of + pairs. As JSON requires that keys must be of type string, rule 2 is the + weakest constraint one can pose on initializer lists to interpret them + as an object. + 3. In all other cases, the initializer list could not be interpreted as + JSON object type, so interpreting it as JSON array type is safe. + + With the rules described above, the following JSON values cannot be + expressed by an initializer list: + + - the empty array (`[]`): use @ref array(initializer_list_t) + with an empty initializer list in this case + - arrays whose elements satisfy rule 2: use @ref + array(initializer_list_t) with the same initializer list + in this case + + @note When used without parentheses around an empty initializer list, @ref + basic_json() is called instead of this function, yielding the JSON null + value. + + @param[in] init initializer list with JSON values + + @param[in] type_deduction internal parameter; when set to `true`, the type + of the JSON value is deducted from the initializer list @a init; when set + to `false`, the type provided via @a manual_type is forced. This mode is + used by the functions @ref array(initializer_list_t) and + @ref object(initializer_list_t). + + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref + value_t::array and @ref value_t::object are valid); when @a type_deduction + is set to `true`, this parameter has no effect + + @throw type_error.301 if @a type_deduction is `false`, @a manual_type is + `value_t::object`, but @a init contains an element which is not a pair + whose first element is a string. In this case, the constructor could not + create an object. If @a type_deduction would have be `true`, an array + would have been created. See @ref object(initializer_list_t) + for an example. + + @complexity Linear in the size of the initializer list @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows how JSON values are created from + initializer lists.,basic_json__list_init_t} + + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + basic_json(initializer_list_t init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const detail::json_ref& element_ref) + { + return (element_ref->is_array() and element_ref->size() == 2 and (*element_ref)[0].is_string()); + }); + + // adjust type if type deduction is not wanted + if (not type_deduction) + { + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if object is wanted but impossible, throw an exception + if (JSON_UNLIKELY(manual_type == value_t::object and not is_an_object)) + { + JSON_THROW(type_error::create(301, "cannot create object from initializer list")); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create object + m_type = value_t::object; + m_value = value_t::object; + + std::for_each(init.begin(), init.end(), [this](const detail::json_ref& element_ref) + { + auto element = element_ref.moved_or_copied(); + m_value.object->emplace( + std::move(*((*element.m_value.array)[0].m_value.string)), + std::move((*element.m_value.array)[1])); + }); + } + else + { + // the initializer list describes an array -> create array + m_type = value_t::array; + m_value.array = create(init.begin(), init.end()); + } + + assert_invariant(); + } + + /*! + @brief explicitly create an array from an initializer list + + Creates a JSON array value from a given initializer list. That is, given a + list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the + initializer list is empty, the empty array `[]` is created. + + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref + basic_json(initializer_list_t, bool, value_t)). These cases + are: + 1. creating an array whose elements are all pairs whose first element is a + string -- in this case, the initializer list constructor would create an + object, taking the first elements as keys + 2. creating an empty array -- passing the empty initializer list to the + initializer list constructor yields an empty object + + @param[in] init initializer list with JSON values to create an array from + (optional) + + @return JSON array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `array` + function.,array} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + static basic_json array(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::array); + } + + /*! + @brief explicitly create an object from an initializer list + + Creates a JSON object value from a given initializer list. The initializer + lists elements must be pairs, and their first elements must be strings. If + the initializer list is empty, the empty object `{}` is created. + + @note This function is only added for symmetry reasons. In contrast to the + related function @ref array(initializer_list_t), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor @ref basic_json(initializer_list_t, bool, value_t). + + @param[in] init initializer list to create an object from (optional) + + @return JSON object value + + @throw type_error.301 if @a init is not a list of pairs whose first + elements are strings. In this case, no object can be created. When such a + value is passed to @ref basic_json(initializer_list_t, bool, value_t), + an array would have been created from the passed initializer list @a init. + See example below. + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `object` + function.,object} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + + @since version 1.0.0 + */ + static basic_json object(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::object); + } + + /*! + @brief construct an array with count copies of given value + + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. + + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy + + @post `std::distance(begin(),end()) == cnt` holds. + + @complexity Linear in @a cnt. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows examples for the @ref + basic_json(size_type\, const basic_json&) + constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 + */ + basic_json(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create(cnt, val); + assert_invariant(); + } + + /*! + @brief construct a JSON container given an iterator range + + Constructs the JSON value with the contents of the range `[first, last)`. + The semantics depends on the different types a JSON value can have: + - In case of a null type, invalid_iterator.206 is thrown. + - In case of other primitive types (number, boolean, or string), @a first + must be `begin()` and @a last must be `end()`. In this case, the value is + copied. Otherwise, invalid_iterator.204 is thrown. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector` or `std::map`; that is, a JSON array + or object is constructed from the values in the range. + + @tparam InputIT an input iterator type (@ref iterator or @ref + const_iterator) + + @param[in] first begin of the range to copy from (included) + @param[in] last end of the range to copy from (excluded) + + @pre Iterators @a first and @a last must be initialized. **This + precondition is enforced with an assertion (see warning).** If + assertions are switched off, a violation of this precondition yields + undefined behavior. + + @pre Range `[first, last)` is valid. Usually, this precondition cannot be + checked efficiently. Only certain edge cases are detected; see the + description of the exceptions below. A violation of this precondition + yields undefined behavior. + + @warning A precondition is enforced with a runtime assertion that will + result in calling `std::abort` if this precondition is not met. + Assertions can be disabled by defining `NDEBUG` at compile time. + See http://en.cppreference.com/w/cpp/error/assert for more + information. + + @throw invalid_iterator.201 if iterators @a first and @a last are not + compatible (i.e., do not belong to the same JSON value). In this case, + the range `[first, last)` is undefined. + @throw invalid_iterator.204 if iterators @a first and @a last belong to a + primitive type (number, boolean, or string), but @a first does not point + to the first element any more. In this case, the range `[first, last)` is + undefined. See example code below. + @throw invalid_iterator.206 if iterators @a first and @a last belong to a + null value. In this case, the range `[first, last)` is undefined. + + @complexity Linear in distance between @a first and @a last. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows several ways to create JSON values by + specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type = 0> + basic_json(InputIT first, InputIT last) + { + assert(first.m_object != nullptr); + assert(last.m_object != nullptr); + + // make sure iterator fits the current value + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); + } + + // copy type from first iterator + m_type = first.m_object->m_type; + + // check if iterator range is complete for primitive values + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_UNLIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + break; + } + + default: + break; + } + + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value.number_float = first.m_object->m_value.number_float; + break; + } + + case value_t::boolean: + { + m_value.boolean = first.m_object->m_value.boolean; + break; + } + + case value_t::string: + { + m_value = *first.m_object->m_value.string; + break; + } + + case value_t::object: + { + m_value.object = create(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_value.array = create(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + + std::string(first.m_object->type_name()))); + } + + assert_invariant(); + } + + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + /// @private + basic_json(const detail::json_ref& ref) + : basic_json(ref.moved_or_copied()) + {} + + /*! + @brief copy constructor + + Creates a copy of a given JSON value. + + @param[in] other the JSON value to copy + + @post `*this == other` + + @complexity Linear in the size of @a other. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - As postcondition, it holds: `other == basic_json(other)`. + + @liveexample{The following code shows an example for the copy + constructor.,basic_json__basic_json} + + @since version 1.0.0 + */ + basic_json(const basic_json& other) + : m_type(other.m_type) + { + // check of passed value is valid + other.assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + m_value = *other.m_value.object; + break; + } + + case value_t::array: + { + m_value = *other.m_value.array; + break; + } + + case value_t::string: + { + m_value = *other.m_value.string; + break; + } + + case value_t::boolean: + { + m_value = other.m_value.boolean; + break; + } + + case value_t::number_integer: + { + m_value = other.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value = other.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value = other.m_value.number_float; + break; + } + + default: + break; + } + + assert_invariant(); + } + + /*! + @brief move constructor + + Move constructor. Constructs a JSON value with the contents of the given + value @a other using move semantics. It "steals" the resources from @a + other and leaves it as JSON null value. + + @param[in,out] other value to move to this object + + @post `*this` has the same value as @a other before the call. + @post @a other is a JSON null value. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @requirement This function helps `basic_json` satisfying the + [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible) + requirements. + + @liveexample{The code below shows the move constructor explicitly called + via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 + */ + basic_json(basic_json&& other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) + { + // check that passed value is valid + other.assert_invariant(); + + // invalidate payload + other.m_type = value_t::null; + other.m_value = {}; + + assert_invariant(); + } + + /*! + @brief copy assignment + + Copy assignment operator. Copies a JSON value via the "copy and swap" + strategy: It is expressed in terms of the copy constructor, destructor, + and the `swap()` member function. + + @param[in] other value to copy from + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + + @liveexample{The code below shows and example for the copy assignment. It + creates a copy of value `a` which is then swapped with `b`. Finally\, the + copy of `a` (which is the null value after the swap) is + destroyed.,basic_json__copyassignment} + + @since version 1.0.0 + */ + reference& operator=(basic_json other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + // check that passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + + assert_invariant(); + return *this; + } + + /*! + @brief destructor + + Destroys the JSON value and frees all allocated memory. + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 + */ + ~basic_json() noexcept + { + assert_invariant(); + m_value.destroy(m_type); + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /*! + @brief serialization + + Serialization function for JSON values. The function tries to mimic + Python's `json.dumps()` function, and currently supports its @a indent + and @a ensure_ascii parameters. + + @param[in] indent If indent is nonnegative, then array elements and object + members will be pretty-printed with that indent level. An indent level of + `0` will only insert newlines. `-1` (the default) selects the most compact + representation. + @param[in] indent_char The character to use for indentation if @a indent is + greater than `0`. The default is ` ` (space). + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists + of ASCII characters only. + + @return string containing the serialization of the JSON value + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + + @complexity Linear. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @liveexample{The following example shows the effect of different @a indent\, + @a indent_char\, and @a ensure_ascii parameters to the result of the + serialization.,dump} + + @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0; indentation character @a indent_char, option + @a ensure_ascii and exceptions added in version 3.0.0 + */ + string_t dump(const int indent = -1, const char indent_char = ' ', + const bool ensure_ascii = false) const + { + string_t result; + serializer s(detail::output_adapter(result), indent_char); + + if (indent >= 0) + { + s.dump(*this, true, ensure_ascii, static_cast(indent)); + } + else + { + s.dump(*this, false, ensure_ascii, 0); + } + + return result; + } + + /*! + @brief return the type of the JSON value (explicit) + + Return the type of the JSON value as a value from the @ref value_t + enumeration. + + @return the type of the JSON value + Value type | return value + ------------------------- | ------------------------- + null | value_t::null + boolean | value_t::boolean + string | value_t::string + number (integer) | value_t::number_integer + number (unsigned integer) | value_t::number_unsigned + number (floating-point) | value_t::number_float + object | value_t::object + array | value_t::array + discarded | value_t::discarded + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `type()` for all JSON + types.,type} + + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr value_t type() const noexcept + { + return m_type; + } + + /*! + @brief return whether type is primitive + + This function returns true if and only if the JSON type is primitive + (string, number, boolean, or null). + + @return `true` if type is primitive (string, number, boolean, or null), + `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_primitive()` for all JSON + types.,is_primitive} + + @sa @ref is_structured() -- returns whether JSON value is structured + @sa @ref is_null() -- returns whether JSON value is `null` + @sa @ref is_string() -- returns whether JSON value is a string + @sa @ref is_boolean() -- returns whether JSON value is a boolean + @sa @ref is_number() -- returns whether JSON value is a number + + @since version 1.0.0 + */ + constexpr bool is_primitive() const noexcept + { + return is_null() or is_string() or is_boolean() or is_number(); + } + + /*! + @brief return whether type is structured + + This function returns true if and only if the JSON type is structured + (array or object). + + @return `true` if type is structured (array or object), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_structured()` for all JSON + types.,is_structured} + + @sa @ref is_primitive() -- returns whether value is primitive + @sa @ref is_array() -- returns whether value is an array + @sa @ref is_object() -- returns whether value is an object + + @since version 1.0.0 + */ + constexpr bool is_structured() const noexcept + { + return is_array() or is_object(); + } + + /*! + @brief return whether value is null + + This function returns true if and only if the JSON value is null. + + @return `true` if type is null, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_null()` for all JSON + types.,is_null} + + @since version 1.0.0 + */ + constexpr bool is_null() const noexcept + { + return (m_type == value_t::null); + } + + /*! + @brief return whether value is a boolean + + This function returns true if and only if the JSON value is a boolean. + + @return `true` if type is boolean, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_boolean()` for all JSON + types.,is_boolean} + + @since version 1.0.0 + */ + constexpr bool is_boolean() const noexcept + { + return (m_type == value_t::boolean); + } + + /*! + @brief return whether value is a number + + This function returns true if and only if the JSON value is a number. This + includes both integer (signed and unsigned) and floating-point values. + + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number()` for all JSON + types.,is_number} + + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number() const noexcept + { + return is_number_integer() or is_number_float(); + } + + /*! + @brief return whether value is an integer number + + This function returns true if and only if the JSON value is a signed or + unsigned integer number. This excludes floating-point values. + + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_integer()` for all + JSON types.,is_number_integer} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number_integer() const noexcept + { + return (m_type == value_t::number_integer or m_type == value_t::number_unsigned); + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true if and only if the JSON value is an unsigned + integer number. This excludes floating-point and signed integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_unsigned()` for all + JSON types.,is_number_unsigned} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + constexpr bool is_number_unsigned() const noexcept + { + return (m_type == value_t::number_unsigned); + } + + /*! + @brief return whether value is a floating-point number + + This function returns true if and only if the JSON value is a + floating-point number. This excludes signed and unsigned integer values. + + @return `true` if type is a floating-point number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_float()` for all + JSON types.,is_number_float} + + @sa @ref is_number() -- check if value is number + @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + + @since version 1.0.0 + */ + constexpr bool is_number_float() const noexcept + { + return (m_type == value_t::number_float); + } + + /*! + @brief return whether value is an object + + This function returns true if and only if the JSON value is an object. + + @return `true` if type is object, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_object()` for all JSON + types.,is_object} + + @since version 1.0.0 + */ + constexpr bool is_object() const noexcept + { + return (m_type == value_t::object); + } + + /*! + @brief return whether value is an array + + This function returns true if and only if the JSON value is an array. + + @return `true` if type is array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_array()` for all JSON + types.,is_array} + + @since version 1.0.0 + */ + constexpr bool is_array() const noexcept + { + return (m_type == value_t::array); + } + + /*! + @brief return whether value is a string + + This function returns true if and only if the JSON value is a string. + + @return `true` if type is string, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_string()` for all JSON + types.,is_string} + + @since version 1.0.0 + */ + constexpr bool is_string() const noexcept + { + return (m_type == value_t::string); + } + + /*! + @brief return whether value is discarded + + This function returns true if and only if the JSON value was discarded + during parsing with a callback function (see @ref parser_callback_t). + + @note This function will always be `false` for JSON values after parsing. + That is, discarded values can only occur during parsing, but will be + removed when inside a structured value or replaced by null in other cases. + + @return `true` if type is discarded, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_discarded()` for all JSON + types.,is_discarded} + + @since version 1.0.0 + */ + constexpr bool is_discarded() const noexcept + { + return (m_type == value_t::discarded); + } + + /*! + @brief return the type of the JSON value (implicit) + + Implicitly return the type of the JSON value as a value from the @ref + value_t enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies the @ref value_t operator for + all JSON types.,operator__value_t} + + @sa @ref type() -- return the type of the JSON value (explicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept + { + return m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get a boolean (explicit) + boolean_t get_impl(boolean_t* /*unused*/) const + { + if (JSON_LIKELY(is_boolean())) + { + return m_value.boolean; + } + + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name()))); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t* /*unused*/) noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t* /*unused*/) noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t* /*unused*/) noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This function helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw type_error.303 if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // delegate the call to get_ptr<>() + auto ptr = obj.template get_ptr::type>(); + + if (JSON_LIKELY(ptr != nullptr)) + { + return *ptr; + } + + JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name()))); + } + + public: + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template::type, basic_json_t>::value, + int> = 0> + basic_json get() const + { + return *this; + } + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value + which is [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const basic_json&, ValueType&)`, and + - @ref json_serializer does not have a `from_json()` method of + the form `ValueType from_json(const basic_json&)` + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get__ValueType_const} + + @since version 2.1.0 + */ + template, + detail::enable_if_t < + not std::is_same::value and + detail::has_from_json::value and + not detail::has_non_default_from_json::value, + int> = 0> + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), std::declval()))) + { + // we cannot static_assert on ValueTypeCV being non-const, because + // there is support for get(), which is why we + // still need the uncvref + static_assert(not std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + static_assert(std::is_default_constructible::value, + "types must be DefaultConstructible when used with get()"); + + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + } + + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer has a `from_json()` method of the form + `ValueType from_json(const basic_json&)` + + @note If @ref json_serializer has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @since version 2.1.0 + */ + template, + detail::enable_if_t::value and + detail::has_non_default_from_json::value, + int> = 0> + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval()))) + { + static_assert(not std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return JSONSerializer::from_json(*this); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get() noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template::value, int>::type = 0> + constexpr const PointerType get() const noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (implicit) + + Implicit pointer access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the pointee of the result yields an undefined + state. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. Enforced by a static + assertion. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get_ptr} + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get_ptr() noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (implicit) + @copydoc get_ptr() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + constexpr const PointerType get_ptr() const noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a reference value (implicit) + + Implicit reference access to the internally stored JSON value. No copies + are made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. Enforced by static assertion. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + type_error.303 otherwise + + @throw type_error.303 in case passed type @a ReferenceType is incompatible + with the stored JSON value; see example below + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.1.0 + */ + template::value, int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw type_error.302 in case passed type @a ValueType is incompatible + to the JSON value type (e.g., the JSON value is of type boolean, but a + string is requested); see example below + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename std::enable_if < + not std::is_pointer::value and + not std::is_same>::value and + not std::is_same::value +#ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015 + and not std::is_same>::value +#endif +#if defined(JSON_HAS_CPP_17) + and not std::is_same::value +#endif + , int >::type = 0 > + operator ValueType() const + { + // delegate the call to get<>() const + return get(); + } + + /// @} + + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__size_type} + */ + reference at(size_type idx) + { + // at only works for arrays + if (JSON_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, + with bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__size_type_const} + */ + const_reference at(size_type idx) const + { + // at only works for arrays + if (JSON_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__object_t_key_type} + */ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, + with bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__object_t_key_type_const} + */ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array or null; in that + cases, using the [] operator with an index makes no sense. + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using `[]` operator. Note the addition of `null` + values.,operatorarray__size_type} + + @since version 1.0.0 + */ + reference operator[](size_type idx) + { + // implicitly convert null value to an empty array + if (is_null()) + { + m_type = value_t::array; + m_value.array = create(); + assert_invariant(); + } + + // operator[] only works for arrays + if (JSON_LIKELY(is_array())) + { + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) + { + m_value.array->insert(m_value.array->end(), + idx - m_value.array->size() + 1, + basic_json()); + } + + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array; in that case, + using the [] operator with an index makes no sense. + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the `[]` operator.,operatorarray__size_type_const} + + @since version 1.0.0 + */ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (JSON_LIKELY(is_array())) + { + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference operator[](const typename object_t::key_type& key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (JSON_LIKELY(is_object())) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + reference operator[](T* key) + { + // implicitly convert null to object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // at only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + const_reference operator[](T* key) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.306 if the JSON value is not an object; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + template::value, int>::type = 0> + ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + + return default_value; + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /*! + @brief access specified object element via JSON Pointer with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(ptr); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const json_pointer&), this function does not throw + if the given key @a key was not found. + + @param[in] ptr a JSON pointer to the element to access + @param[in] default_value the value to return if @a ptr found no value + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.306 if the JSON value is not an objec; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value_ptr} + + @sa @ref operator[](const json_pointer&) for unchecked access by reference + + @since version 2.0.2 + */ + template::value, int>::type = 0> + ValueType value(const json_pointer& ptr, const ValueType& default_value) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + // if pointer resolves a value, return it or use default value + JSON_TRY + { + return ptr.get_checked(this); + } + JSON_CATCH (out_of_range&) + { + return default_value; + } + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const json_pointer&, ValueType) const + */ + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /*! + @brief access the first element + + Returns a reference to the first element in the container. For a JSON + container `c`, the expression `c.front()` is equivalent to `*c.begin()`. + + @return In case of a structured type (array or object), a reference to the + first element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on `null` value + + @liveexample{The following code shows an example for `front()`.,front} + + @sa @ref back() -- access the last element + + @since version 1.0.0 + */ + reference front() + { + return *begin(); + } + + /*! + @copydoc basic_json::front() + */ + const_reference front() const + { + return *cbegin(); + } + + /*! + @brief access the last element + + Returns a reference to the last element in the container. For a JSON + container `c`, the expression `c.back()` is equivalent to + @code {.cpp} + auto tmp = c.end(); + --tmp; + return *tmp; + @endcode + + @return In case of a structured type (array or object), a reference to the + last element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on a `null` value. See example + below. + + @liveexample{The following code shows an example for `back()`.,back} + + @sa @ref front() -- access the first element + + @since version 1.0.0 + */ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /*! + @copydoc basic_json::back() + */ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /*! + @brief remove element given an iterator + + Removes the element specified by iterator @a pos. The iterator @a pos must + be valid and dereferenceable. Thus the `end()` iterator (which is valid, + but is not dereferenceable) cannot be used as a value for @a pos. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] pos iterator to the element to remove + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.202 if called on an iterator which does not belong + to the current JSON value; example: `"iterator does not fit current + value"` + @throw invalid_iterator.205 if called on a primitive type with invalid + iterator (i.e., any iterator which is not `begin()`); example: `"iterator + out of range"` + + @complexity The complexity depends on the type: + - objects: amortized constant + - arrays: linear in distance between @a pos and the end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType} + + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType pos) + { + // make sure iterator fits the current value + if (JSON_UNLIKELY(this != pos.m_object)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_UNLIKELY(not pos.m_it.primitive_iterator.is_begin())) + { + JSON_THROW(invalid_iterator::create(205, "iterator out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove elements given an iterator range + + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] first iterator to the beginning of the range to remove + @param[in] last iterator past the end of the range to remove + @return Iterator following the last removed element. If the iterator @a + second refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.203 if called on iterators which does not belong + to the current JSON value; example: `"iterators do not fit current value"` + @throw invalid_iterator.204 if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` + + @complexity The complexity depends on the type: + - objects: `log(size()) + std::distance(first, last)` + - arrays: linear in the distance between @a first and @a last, plus linear + in the distance between @a last and end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType_IteratorType} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType first, IteratorType last) + { + // make sure iterator fits the current value + if (JSON_UNLIKELY(this != first.m_object or this != last.m_object)) + { + JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_LIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove element from a JSON object given a key + + Removes elements from a JSON object with the key value @a key. + + @param[in] key value of the elements to remove + + @return Number of elements removed. If @a ObjectType is the default + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). + + @post References and iterators to the erased elements are invalidated. + Other references and iterators are not affected. + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + + @complexity `log(size()) + count(key)` + + @liveexample{The example shows the effect of `erase()`.,erase__key_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + size_type erase(const typename object_t::key_type& key) + { + // this erase only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->erase(key); + } + + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + /*! + @brief remove element from a JSON array given an index + + Removes element from a JSON array at the index @a idx. + + @param[in] idx index of the element to remove + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + @throw out_of_range.401 when `idx >= size()`; example: `"array index 17 + is out of range"` + + @complexity Linear in distance between @a idx and the end of the container. + + @liveexample{The example shows the effect of `erase()`.,erase__size_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 + */ + void erase(const size_type idx) + { + // this erase only works for arrays + if (JSON_LIKELY(is_array())) + { + if (JSON_UNLIKELY(idx >= size())) + { + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else + { + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + } + + /// @} + + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /*! + @brief find an element in a JSON object + + Finds an element in a JSON object with key equivalent to @a key. If the + element is not found or the JSON value is not an object, end() is + returned. + + @note This method always returns @ref end() when executed on a JSON type + that is not an object. + + @param[in] key key value of the element to search for. + + @return Iterator to an element with key equivalent to @a key. If no such + element is found or the JSON value is not an object, past-the-end (see + @ref end()) iterator is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `find()` is used.,find__key_type} + + @since version 1.0.0 + */ + template + iterator find(KeyT&& key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief find an element in a JSON object + @copydoc find(KeyT&&) + */ + template + const_iterator find(KeyT&& key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief returns the number of occurrences of a key in a JSON object + + Returns the number of elements with key @a key. If ObjectType is the + default `std::map` type, the return value will always be `0` (@a key was + not found) or `1` (@a key was found). + + @note This method always returns `0` when executed on a JSON type that is + not an object. + + @param[in] key key value of the element to count + + @return Number of elements with key @a key. If the JSON value is not an + object, the return value will be `0`. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `count()` is used.,count} + + @since version 1.0.0 + */ + template + size_type count(KeyT&& key) const + { + // return 0 for all nonobject types + return is_object() ? m_value.object->count(std::forward(key)) : 0; + } + + /// @} + + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /*! + @brief returns an iterator to the first element + + Returns an iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `begin()`.,begin} + + @sa @ref cbegin() -- returns a const iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /*! + @copydoc basic_json::cbegin() + */ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /*! + @brief returns a const iterator to the first element + + Returns a const iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).begin()`. + + @liveexample{The following code shows an example for `cbegin()`.,cbegin} + + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /*! + @brief returns an iterator to one past the last element + + Returns an iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `end()`.,end} + + @sa @ref cend() -- returns a const iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /*! + @copydoc basic_json::cend() + */ + const_iterator end() const noexcept + { + return cend(); + } + + /*! + @brief returns a const iterator to one past the last element + + Returns a const iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).end()`. + + @liveexample{The following code shows an example for `cend()`.,cend} + + @sa @ref end() -- returns an iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /*! + @brief returns an iterator to the reverse-beginning + + Returns an iterator to the reverse-beginning; that is, the last element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(end())`. + + @liveexample{The following code shows an example for `rbegin()`.,rbegin} + + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /*! + @copydoc basic_json::crbegin() + */ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /*! + @brief returns an iterator to the reverse-end + + Returns an iterator to the reverse-end; that is, one before the first + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(begin())`. + + @liveexample{The following code shows an example for `rend()`.,rend} + + @sa @ref crend() -- returns a const reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /*! + @copydoc basic_json::crend() + */ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /*! + @brief returns a const reverse iterator to the last element + + Returns a const iterator to the reverse-beginning; that is, the last + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rbegin()`. + + @liveexample{The following code shows an example for `crbegin()`.,crbegin} + + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /*! + @brief returns a const reverse iterator to one before the first + + Returns a const reverse iterator to the reverse-end; that is, one before + the first element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rend()`. + + @liveexample{The following code shows an example for `crend()`.,crend} + + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without iterator_wrapper: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without iterator proxy: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with iterator proxy: + + @code{cpp} + for (auto it : json::iterator_wrapper(j_object)) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). + + @param[in] ref reference to a JSON value + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the wrapper is used,iterator_wrapper} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @note The name of this function is not yet final and may change in the + future. + + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use @ref items() instead; + that is, replace `json::iterator_wrapper(j)` with `j.items()`. + */ + JSON_DEPRECATED + static iteration_proxy iterator_wrapper(reference ref) noexcept + { + return ref.items(); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + JSON_DEPRECATED + static iteration_proxy iterator_wrapper(const_reference ref) noexcept + { + return ref.items(); + } + + /*! + @brief helper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without `items()` function: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without `items()` function: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with `items()` function: + + @code{cpp} + for (auto it : j_object.items()) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). For primitive types (e.g., numbers), + `key()` returns an empty string. + + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the function is used.,items} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 3.x.x. + */ + iteration_proxy items() noexcept + { + return iteration_proxy(*this); + } + + /*! + @copydoc items() + */ + iteration_proxy items() const noexcept + { + return iteration_proxy(*this); + } + + /// @} + + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /*! + @brief checks whether the container is empty. + + Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `true` + boolean | `false` + string | `false` + number | `false` + object | result of function `object_t::empty()` + array | result of function `array_t::empty()` + + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `begin() == end()`. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + bool empty() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_value.object->empty(); + } + + default: + { + // all other types are nonempty + return false; + } + } + } + + /*! + @brief returns the number of elements + + Returns the number of elements in a JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` + boolean | `1` + string | `1` + number | `1` + object | result of function object_t::size() + array | result of function array_t::size() + + @liveexample{The following code calls `size()` on the different value + types.,size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `std::distance(begin(), end())`. + + @sa @ref empty() -- checks whether the container is empty + @sa @ref max_size() -- returns the maximal number of elements + + @since version 1.0.0 + */ + size_type size() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_value.object->size(); + } + + default: + { + // all other types have size 1 + return 1; + } + } + } + + /*! + @brief returns the maximum possible number of elements + + Returns the maximum number of elements a JSON value is able to hold due to + system or library implementation limitations, i.e. `std::distance(begin(), + end())` for the JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` (same as `size()`) + boolean | `1` (same as `size()`) + string | `1` (same as `size()`) + number | `1` (same as `size()`) + object | result of function `object_t::max_size()` + array | result of function `array_t::max_size()` + + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of returning `b.size()` where `b` is the largest + possible JSON value. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + size_type max_size() const noexcept + { + switch (m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_value.object->max_size(); + } + + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /*! + @brief clears the contents + + Clears the content of a JSON value and resets it to the default value as + if @ref basic_json(value_t) would have been called with the current value + type from @ref type(): + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @post Has the same effect as calling + @code {.cpp} + *this = basic_json(type()); + @endcode + + @liveexample{The example below shows the effect of `clear()` to different + JSON types.,clear} + + @complexity Linear in the size of the JSON value. + + @iterators All iterators, pointers and references related to this container + are invalidated. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @sa @ref basic_json(value_t) -- constructor that creates an object with the + same value than calling `clear()` + + @since version 1.0.0 + */ + void clear() noexcept + { + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: + { + m_value.number_float = 0.0; + break; + } + + case value_t::boolean: + { + m_value.boolean = false; + break; + } + + case value_t::string: + { + m_value.string->clear(); + break; + } + + case value_t::array: + { + m_value.array->clear(); + break; + } + + case value_t::object: + { + m_value.object->clear(); + break; + } + + default: + break; + } + } + + /*! + @brief add an object to an array + + Appends the given element @a val to the end of the JSON value. If the + function is called on a JSON null value, an empty array is created before + appending @a val. + + @param[in] val the value to add to the JSON array + + @throw type_error.308 when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON array. Note how the `null` value was silently + converted to a JSON array.,push_back} + + @since version 1.0.0 + */ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (move semantics) + m_value.array->push_back(std::move(val)); + // invalidate object + val.m_type = value_t::null; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array + m_value.array->push_back(val); + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + Inserts the given element @a val to the JSON object. If the function is + called on a JSON null value, an empty object is created before inserting + @a val. + + @param[in] val the value to add to the JSON object + + @throw type_error.308 when called on a type other than JSON object or + null; example: `"cannot use push_back() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON object. Note how the `null` value was silently + converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 + */ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (JSON_UNLIKELY(not(is_null() or is_object()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array + m_value.object->insert(val); + } + + /*! + @brief add an object to an object + @copydoc push_back(const typename object_t::value_type&) + */ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param[in] init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(initializer_list_t init) + { + if (is_object() and init.size() == 2 and (*init.begin())->is_string()) + { + basic_json&& key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(initializer_list_t) + */ + reference operator+=(initializer_list_t init) + { + push_back(init); + return *this; + } + + /*! + @brief add an object to an array + + Creates a JSON value from the passed parameters @a args to the end of the + JSON value. If the function is called on a JSON null value, an empty array + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @throw type_error.311 when called on a type other than JSON array or + null; example: `"cannot use emplace_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` can be used to add + elements to a JSON array. Note how the `null` value was silently converted + to a JSON array.,emplace_back} + + @since version 2.0.8 + */ + template + void emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) + m_value.array->emplace_back(std::forward(args)...); + } + + /*! + @brief add an object to an object if key does not exist + + Inserts a new element into a JSON object constructed in-place with the + given @a args if there is no element with the key in the container. If the + function is called on a JSON null value, an empty object is created before + appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return a pair consisting of an iterator to the inserted element, or the + already-existing element if no insertion happened, and a bool + denoting whether the insertion took place. + + @throw type_error.311 when called on a type other than JSON object or + null; example: `"cannot use emplace() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `emplace()` can be used to add elements + to a JSON object. Note how the `null` value was silently converted to a + JSON object. Further note how no value is added if there was already one + value stored with the same key.,emplace} + + @since version 2.0.8 + */ + template + std::pair emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_object()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + auto res = m_value.object->emplace(std::forward(args)...); + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; + } + + /*! + @brief inserts element + + Inserts element @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] val element to insert + @return iterator pointing to the inserted @a val. + + @throw type_error.309 if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Constant plus linear in the distance between @a pos and end of + the container. + + @liveexample{The example shows how `insert()` is used.,insert} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const basic_json& val) + { + // insert only works for arrays + if (JSON_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts element + @copydoc insert(const_iterator, const basic_json&) + */ + iterator insert(const_iterator pos, basic_json&& val) + { + return insert(pos, val); + } + + /*! + @brief inserts elements + + Inserts @a cnt copies of @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert + @return iterator pointing to the first element inserted, or @a pos if + `cnt==0` + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Linear in @a cnt plus linear in the distance between @a pos + and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__count} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + // insert only works for arrays + if (JSON_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)` before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + @throw invalid_iterator.211 if @a first or @a last are iterators into + container for which insert is called; example: `"passed iterators may not + belong to container"` + + @return iterator pointing to the first element inserted, or @a pos if + `first==last` + + @complexity Linear in `std::distance(first, last)` plus linear in the + distance between @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__range} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + // insert only works for arrays + if (JSON_UNLIKELY(not is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + if (JSON_UNLIKELY(first.m_object == this)) + { + JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert( + pos.m_it.array_iterator, + first.m_it.array_iterator, + last.m_it.array_iterator); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from initializer list @a ilist before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] ilist initializer list to insert the values from + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @return iterator pointing to the first element inserted, or @a pos if + `ilist` is empty + + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__ilist} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, initializer_list_t ilist) + { + // insert only works for arrays + if (JSON_UNLIKELY(not is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end()); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)`. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than objects; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number + of elements to insert. + + @liveexample{The example shows how `insert()` is used.,insert__range_object} + + @since version 3.0.0 + */ + void insert(const_iterator first, const_iterator last) + { + // insert only works for objects + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_UNLIKELY(not first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from JSON object @a j and overwrites existing keys. + + @param[in] j JSON object to read values from + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_reference j) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()))); + } + + for (auto it = j.cbegin(); it != j.cend(); ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from from range `[first, last)` and overwrites existing + keys. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used__range.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_iterator first, const_iterator last) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_UNLIKELY(not first.m_object->is_object() + or not first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + for (auto it = first; it != last; ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); + assert_invariant(); + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw type_error.310 when JSON value is not an array; example: `"cannot + use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how arrays can be swapped with + `swap()`.,swap__array_t} + + @since version 1.0.0 + */ + void swap(array_t& other) + { + // swap only works for arrays + if (JSON_LIKELY(is_array())) + { + std::swap(*(m_value.array), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON object with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other object to exchange the contents with + + @throw type_error.310 when JSON value is not an object; example: + `"cannot use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how objects can be swapped with + `swap()`.,swap__object_t} + + @since version 1.0.0 + */ + void swap(object_t& other) + { + // swap only works for objects + if (JSON_LIKELY(is_object())) + { + std::swap(*(m_value.object), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other string to exchange the contents with + + @throw type_error.310 when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__string_t} + + @since version 1.0.0 + */ + void swap(string_t& other) + { + // swap only works for strings + if (JSON_LIKELY(is_string())) + { + std::swap(*(m_value.string), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /// @} + + public: + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + /*! + @brief comparison: equal + + Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) they are from the same type and (2) + their stored values are the same according to their respective + `operator==`. + - Integer and floating-point numbers are automatically converted before + comparison. Note than two NaN values are always treated as unequal. + - Two JSON null values are equal. + + @note Floating-point inside JSON values numbers are compared with + `json::number_float_t::operator==` which is `double::operator==` by + default. To compare floating-point while respecting an epsilon, an alternative + [comparison function](https://github.com/mariokonrad/marnav/blob/master/src/marnav/math/floatingpoint.hpp#L34-#L39) + could be used, for instance + @code {.cpp} + template::value, T>::type> + inline bool is_same(T a, T b, T epsilon = std::numeric_limits::epsilon()) noexcept + { + return std::abs(a - b) <= epsilon; + } + @endcode + + @note NaN values never compare equal to themselves or to other NaN values. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are equal + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__equal} + + @since version 1.0.0 + */ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + return (*lhs.m_value.array == *rhs.m_value.array); + + case value_t::object: + return (*lhs.m_value.object == *rhs.m_value.object); + + case value_t::null: + return true; + + case value_t::string: + return (*lhs.m_value.string == *rhs.m_value.string); + + case value_t::boolean: + return (lhs.m_value.boolean == rhs.m_value.boolean); + + case value_t::number_integer: + return (lhs.m_value.number_integer == rhs.m_value.number_integer); + + case value_t::number_unsigned: + return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned); + + case value_t::number_float: + return (lhs.m_value.number_float == rhs.m_value.number_float); + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return (static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float); + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return (lhs.m_value.number_float == static_cast(rhs.m_value.number_integer)); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return (static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float); + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return (lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned)); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return (static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return (lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned)); + } + + return false; + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs == basic_json(rhs)); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) == rhs); + } + + /*! + @brief comparison: not equal + + Compares two JSON values for inequality by calculating `not (lhs == rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are not equal + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__notequal} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs == rhs); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs != basic_json(rhs)); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) != rhs); + } + + /*! + @brief comparison: less than + + Compares whether one JSON value @a lhs is less than another JSON value @a + rhs according to the following rules: + - If @a lhs and @a rhs have the same type, the values are compared using + the default `<` operator. + - Integer and floating-point numbers are automatically converted before + comparison + - In case @a lhs and @a rhs have different types, the values are ignored + and the order of the types is considered, see + @ref operator<(const value_t, const value_t). + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__less} + + @since version 1.0.0 + */ + friend bool operator<(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + return (*lhs.m_value.array) < (*rhs.m_value.array); + + case value_t::object: + return *lhs.m_value.object < *rhs.m_value.object; + + case value_t::null: + return false; + + case value_t::string: + return *lhs.m_value.string < *rhs.m_value.string; + + case value_t::boolean: + return lhs.m_value.boolean < rhs.m_value.boolean; + + case value_t::number_integer: + return lhs.m_value.number_integer < rhs.m_value.number_integer; + + case value_t::number_unsigned: + return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; + + case value_t::number_float: + return lhs.m_value.number_float < rhs.m_value.number_float; + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs < basic_json(rhs)); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) < rhs); + } + + /*! + @brief comparison: less than or equal + + Compares whether one JSON value @a lhs is less than or equal to another + JSON value by calculating `not (rhs < lhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greater} + + @since version 1.0.0 + */ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + return not (rhs < lhs); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs <= basic_json(rhs)); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) <= rhs); + } + + /*! + @brief comparison: greater than + + Compares whether one JSON value @a lhs is greater than another + JSON value by calculating `not (lhs <= rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__lessequal} + + @since version 1.0.0 + */ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs <= rhs); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs > basic_json(rhs)); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) > rhs); + } + + /*! + @brief comparison: greater than or equal + + Compares whether one JSON value @a lhs is greater than or equal to another + JSON value by calculating `not (lhs < rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greaterequal} + + @since version 1.0.0 + */ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs < rhs); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs >= basic_json(rhs)); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) >= rhs); + } + + /// @} + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ + + /*! + @brief serialize to stream + + Serialize the given JSON value @a j to the output stream @a o. The JSON + value will be serialized using the @ref dump member function. + + - The indentation of the output can be controlled with the member variable + `width` of the output stream @a o. For instance, using the manipulator + `std::setw(4)` on @a o sets the indentation level to `4` and the + serialization result is the same as calling `dump(4)`. + + - The indentation character can be controlled with the member variable + `fill` of the output stream @a o. For instance, the manipulator + `std::setfill('\\t')` sets indentation to use a tab character rather than + the default space character. + + @param[in,out] o stream to serialize to + @param[in] j JSON value to serialize + + @return the stream @a o + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + + @complexity Linear. + + @liveexample{The example below shows the serialization with different + parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0; indentation character added in version 3.0.0 + */ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as indentation parameter if nonzero + const bool pretty_print = (o.width() > 0); + const auto indentation = (pretty_print ? o.width() : 0); + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // do the actual serialization + serializer s(detail::output_adapter(o), o.fill()); + s.dump(j, pretty_print, false, static_cast(indentation)); + return o; + } + + /*! + @brief serialize to stream + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use + @ref operator<<(std::ostream&, const basic_json&) + instead; that is, replace calls like `j >> o;` with `o << j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_DEPRECATED + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } + + /// @} + + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /*! + @brief deserialize from a compatible input + + This function reads from a compatible input. Examples are: + - an array of 1-byte values + - strings with character/literal type with size of 1 byte + - input streams + - container with contiguous storage of 1-byte values. Compatible container + types include `std::vector`, `std::string`, `std::array`, + `std::valarray`, and `std::initializer_list`. Furthermore, C-style + arrays can be used with `std::begin()`/`std::end()`. User-defined + containers can be used as long as they implement random-access iterators + and a contiguous storage. + + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @pre The container storage is contiguous. Violating this precondition + yields undefined behavior. **This precondition is enforced with an + assertion.** + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with a noncompliant container and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @param[in] i input to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an array.,parse__array__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function reading + from a contiguous container.,parse__contiguouscontainer__parser_callback_t} + + @since version 2.0.3 (contiguous containers) + */ + static basic_json parse(detail::input_adapter i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } + + /*! + @copydoc basic_json parse(detail::input_adapter, const parser_callback_t) + */ + static basic_json parse(detail::input_adapter& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } + + static bool accept(detail::input_adapter i) + { + return parser(i).accept(true); + } + + static bool accept(detail::input_adapter& i) + { + return parser(i).accept(true); + } + + /*! + @brief deserialize from an iterator range with contiguous storage + + This function reads from an iterator range of a container with contiguous + storage of 1-byte values. Compatible container types include + `std::vector`, `std::string`, `std::array`, `std::valarray`, and + `std::initializer_list`. Furthermore, C-style arrays can be used with + `std::begin()`/`std::end()`. User-defined containers can be used as long + as they implement random-access iterators and a contiguous storage. + + @pre The iterator range is contiguous. Violating this precondition yields + undefined behavior. **This precondition is enforced with an assertion.** + @pre Each element in the range has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with noncompliant iterators and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @tparam IteratorType iterator of container with contiguous storage + @param[in] first begin of the range to parse (included) + @param[in] last end of the range to parse (excluded) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return result of the deserialization + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an iterator range.,parse__iteratortype__parser_callback_t} + + @since version 2.0.3 + */ + template::iterator_category>::value, int>::type = 0> + static basic_json parse(IteratorType first, IteratorType last, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result); + return result; + } + + template::iterator_category>::value, int>::type = 0> + static bool accept(IteratorType first, IteratorType last) + { + return parser(detail::input_adapter(first, last)).accept(true); + } + + /*! + @brief deserialize from stream + @deprecated This stream operator is deprecated and will be removed in + version 4.0.0 of the library. Please use + @ref operator>>(std::istream&, basic_json&) + instead; that is, replace calls like `j << i;` with `i >> j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_DEPRECATED + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + return operator>>(i, j); + } + + /*! + @brief deserialize from stream + + Deserializes an input stream to a JSON value. + + @param[in,out] i input stream to read a serialized JSON value from + @param[in,out] j JSON value to write the deserialized input to + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below shows how a JSON value is constructed by + reading a serialization from a stream.,operator_deserialize} + + @sa parse(std::istream&, const parser_callback_t) for a variant with a + parser callback function to filter values while parsing + + @since version 1.0.0 + */ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + parser(detail::input_adapter(i)).parse(false, j); + return i; + } + + /// @} + + /////////////////////////// + // convenience functions // + /////////////////////////// + + /*! + @brief return the type as string + + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. + + @return a string representation of a the @a m_type member: + Value type | return value + ----------- | ------------- + null | `"null"` + boolean | `"boolean"` + string | `"string"` + number | `"number"` (for all number types) + object | `"object"` + array | `"array"` + discarded | `"discarded"` + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Constant. + + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,type_name} + + @sa @ref type() -- return the type of the JSON value + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + + @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` + since 3.0.0 + */ + const char* type_name() const noexcept + { + { + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + } + + + private: + ////////////////////// + // member variables // + ////////////////////// + + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + public: + /*! + @brief create a CBOR serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the CBOR (Concise + Binary Object Representation) serialization format. CBOR is a binary + serialization format which aims to be more compact than JSON itself, yet + more efficient to parse. + + The library uses the following mapping from JSON values types to + CBOR types according to the CBOR specification (RFC 7049): + + JSON value type | value/range | CBOR type | first byte + --------------- | ------------------------------------------ | ---------------------------------- | --------------- + null | `null` | Null | 0xF6 + boolean | `true` | True | 0xF5 + boolean | `false` | False | 0xF4 + number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B + number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A + number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 + number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 + number_integer | -24..-1 | Negative integer | 0x20..0x37 + number_integer | 0..23 | Integer | 0x00..0x17 + number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_unsigned | 0..23 | Integer | 0x00..0x17 + number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_float | *any value* | Double-Precision Float | 0xFB + string | *length*: 0..23 | UTF-8 string | 0x60..0x77 + string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 + string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 + string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A + string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B + array | *size*: 0..23 | array | 0x80..0x97 + array | *size*: 23..255 | array (1 byte follow) | 0x98 + array | *size*: 256..65535 | array (2 bytes follow) | 0x99 + array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A + array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B + object | *size*: 0..23 | map | 0xA0..0xB7 + object | *size*: 23..255 | map (1 byte follow) | 0xB8 + object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 + object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA + object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a CBOR value. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The following CBOR types are not used in the conversion: + - byte strings (0x40..0x5F) + - UTF-8 strings terminated by "break" (0x7F) + - arrays terminated by "break" (0x9F) + - maps terminated by "break" (0xBF) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - tagged items (0xC6..0xD4, 0xD8..0xDB) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + - half and single-precision floats (0xF9-0xFA) + - break (0xFF) + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in CBOR format.,to_cbor} + + @sa http://cbor.io + @sa @ref from_cbor(detail::input_adapter, const bool strict) for the + analogous deserialization + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9 + */ + static std::vector to_cbor(const basic_json& j) + { + std::vector result; + to_cbor(j, result); + return result; + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the MessagePack + serialization format. MessagePack is a binary serialization format which + aims to be more compact than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + MessagePack types according to the MessagePack specification: + + JSON value type | value/range | MessagePack type | first byte + --------------- | --------------------------------- | ---------------- | ---------- + null | `null` | nil | 0xC0 + boolean | `true` | true | 0xC3 + boolean | `false` | false | 0xC2 + number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3 + number_integer | -2147483648..-32769 | int32 | 0xD2 + number_integer | -32768..-129 | int16 | 0xD1 + number_integer | -128..-33 | int8 | 0xD0 + number_integer | -32..-1 | negative fixint | 0xE0..0xFF + number_integer | 0..127 | positive fixint | 0x00..0x7F + number_integer | 128..255 | uint 8 | 0xCC + number_integer | 256..65535 | uint 16 | 0xCD + number_integer | 65536..4294967295 | uint 32 | 0xCE + number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_unsigned | 0..127 | positive fixint | 0x00..0x7F + number_unsigned | 128..255 | uint 8 | 0xCC + number_unsigned | 256..65535 | uint 16 | 0xCD + number_unsigned | 65536..4294967295 | uint 32 | 0xCE + number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_float | *any value* | float 64 | 0xCB + string | *length*: 0..31 | fixstr | 0xA0..0xBF + string | *length*: 32..255 | str 8 | 0xD9 + string | *length*: 256..65535 | str 16 | 0xDA + string | *length*: 65536..4294967295 | str 32 | 0xDB + array | *size*: 0..15 | fixarray | 0x90..0x9F + array | *size*: 16..65535 | array 16 | 0xDC + array | *size*: 65536..4294967295 | array 32 | 0xDD + object | *size*: 0..15 | fix map | 0x80..0x8F + object | *size*: 16..65535 | map 16 | 0xDE + object | *size*: 65536..4294967295 | map 32 | 0xDF + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a MessagePack value. + + @note The following values can **not** be converted to a MessagePack value: + - strings with more than 4294967295 bytes + - arrays with more than 4294967295 elements + - objects with more than 4294967295 elements + + @note The following MessagePack types are not used in the conversion: + - bin 8 - bin 32 (0xC4..0xC6) + - ext 8 - ext 32 (0xC7..0xC9) + - float 32 (0xCA) + - fixext 1 - fixext 16 (0xD4..0xD8) + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in MessagePack format.,to_msgpack} + + @sa http://msgpack.org + @sa @ref from_msgpack(const std::vector&, const size_t) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9 + */ + static std::vector to_msgpack(const basic_json& j) + { + std::vector result; + to_msgpack(j, result); + return result; + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + /*! + @brief create a UBJSON serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the UBJSON + (Universal Binary JSON) serialization format. UBJSON aims to be more compact + than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + UBJSON types according to the UBJSON specification: + + JSON value type | value/range | UBJSON type | marker + --------------- | --------------------------------- | ----------- | ------ + null | `null` | null | `Z` + boolean | `true` | true | `T` + boolean | `false` | false | `F` + number_integer | -9223372036854775808..-2147483649 | int64 | `L` + number_integer | -2147483648..-32769 | int32 | `l` + number_integer | -32768..-129 | int16 | `I` + number_integer | -128..127 | int8 | `i` + number_integer | 128..255 | uint8 | `U` + number_integer | 256..32767 | int16 | `I` + number_integer | 32768..2147483647 | int32 | `l` + number_integer | 2147483648..9223372036854775807 | int64 | `L` + number_unsigned | 0..127 | int8 | `i` + number_unsigned | 128..255 | uint8 | `U` + number_unsigned | 256..32767 | int16 | `I` + number_unsigned | 32768..2147483647 | int32 | `l` + number_unsigned | 2147483648..9223372036854775807 | int64 | `L` + number_float | *any value* | float64 | `D` + string | *with shortest length indicator* | string | `S` + array | *see notes on optimized format* | array | `[` + object | *see notes on optimized format* | map | `{` + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a UBJSON value. + + @note The following values can **not** be converted to a UBJSON value: + - strings with more than 9223372036854775807 bytes (theoretical) + - unsigned integer numbers above 9223372036854775807 + + @note The following markers are not used in the conversion: + - `Z`: no-op values are not created. + - `C`: single-byte strings are serialized with `S` markers. + + @note Any UBJSON output created @ref to_ubjson can be successfully parsed + by @ref from_ubjson. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The optimized formats for containers are supported: Parameter + @a use_size adds size information to the beginning of a container and + removes the closing marker. Parameter @a use_type further checks + whether all elements of a container have the same type and adds the + type marker to the beginning of the container. The @a use_type + parameter must only be used together with @a use_size = true. Note + that @a use_size = true alone may result in larger representations - + the benefit of this parameter is that the receiving side is + immediately informed on the number of elements of the container. + + @param[in] j JSON value to serialize + @param[in] use_size whether to add size annotations to container types + @param[in] use_type whether to add type annotations to container types + (must be combined with @a use_size = true) + @return UBJSON serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in UBJSON format.,to_ubjson} + + @sa http://ubjson.org + @sa @ref from_ubjson(detail::input_adapter, const bool strict) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + + @since version 3.1.0 + */ + static std::vector to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector result; + to_ubjson(j, result, use_size, use_type); + return result; + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + /*! + @brief create a JSON value from an input in CBOR format + + Deserializes a given input @a i to a JSON value using the CBOR (Concise + Binary Object Representation) serialization format. + + The library maps CBOR types to JSON value types as follows: + + CBOR type | JSON value type | first byte + ---------------------- | --------------- | ---------- + Integer | number_unsigned | 0x00..0x17 + Unsigned integer | number_unsigned | 0x18 + Unsigned integer | number_unsigned | 0x19 + Unsigned integer | number_unsigned | 0x1A + Unsigned integer | number_unsigned | 0x1B + Negative integer | number_integer | 0x20..0x37 + Negative integer | number_integer | 0x38 + Negative integer | number_integer | 0x39 + Negative integer | number_integer | 0x3A + Negative integer | number_integer | 0x3B + Negative integer | number_integer | 0x40..0x57 + UTF-8 string | string | 0x60..0x77 + UTF-8 string | string | 0x78 + UTF-8 string | string | 0x79 + UTF-8 string | string | 0x7A + UTF-8 string | string | 0x7B + UTF-8 string | string | 0x7F + array | array | 0x80..0x97 + array | array | 0x98 + array | array | 0x99 + array | array | 0x9A + array | array | 0x9B + array | array | 0x9F + map | object | 0xA0..0xB7 + map | object | 0xB8 + map | object | 0xB9 + map | object | 0xBA + map | object | 0xBB + map | object | 0xBF + False | `false` | 0xF4 + True | `true` | 0xF5 + Nill | `null` | 0xF6 + Half-Precision Float | number_float | 0xF9 + Single-Precision Float | number_float | 0xFA + Double-Precision Float | number_float | 0xFB + + @warning The mapping is **incomplete** in the sense that not all CBOR + types can be converted to a JSON value. The following CBOR types + are not supported and will yield parse errors (parse_error.112): + - byte strings (0x40..0x5F) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - tagged items (0xC6..0xD4, 0xD8..0xDB) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + + @warning CBOR allows map keys of any type, whereas JSON only allows + strings as keys in object values. Therefore, CBOR maps with keys + other than UTF-8 strings are rejected (parse_error.113). + + @note Any CBOR output created @ref to_cbor can be successfully parsed by + @ref from_cbor. + + @param[in] i an input in CBOR format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @return deserialized JSON value + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from CBOR were + used in the given input @a v or if the input is not valid CBOR + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} + + @sa http://cbor.io + @sa @ref to_cbor(const basic_json&) for the analogous serialization + @sa @ref from_msgpack(detail::input_adapter, const bool) for the + related MessagePack format + @sa @ref from_ubjson(detail::input_adapter, const bool) for the related + UBJSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 + */ + static basic_json from_cbor(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_cbor(strict); + } + + /*! + @copydoc from_cbor(detail::input_adapter, const bool) + */ + template::value, int> = 0> + static basic_json from_cbor(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_cbor(strict); + } + + /*! + @brief create a JSON value from an input in MessagePack format + + Deserializes a given input @a i to a JSON value using the MessagePack + serialization format. + + The library maps MessagePack types to JSON value types as follows: + + MessagePack type | JSON value type | first byte + ---------------- | --------------- | ---------- + positive fixint | number_unsigned | 0x00..0x7F + fixmap | object | 0x80..0x8F + fixarray | array | 0x90..0x9F + fixstr | string | 0xA0..0xBF + nil | `null` | 0xC0 + false | `false` | 0xC2 + true | `true` | 0xC3 + float 32 | number_float | 0xCA + float 64 | number_float | 0xCB + uint 8 | number_unsigned | 0xCC + uint 16 | number_unsigned | 0xCD + uint 32 | number_unsigned | 0xCE + uint 64 | number_unsigned | 0xCF + int 8 | number_integer | 0xD0 + int 16 | number_integer | 0xD1 + int 32 | number_integer | 0xD2 + int 64 | number_integer | 0xD3 + str 8 | string | 0xD9 + str 16 | string | 0xDA + str 32 | string | 0xDB + array 16 | array | 0xDC + array 32 | array | 0xDD + map 16 | object | 0xDE + map 32 | object | 0xDF + negative fixint | number_integer | 0xE0-0xFF + + @warning The mapping is **incomplete** in the sense that not all + MessagePack types can be converted to a JSON value. The following + MessagePack types are not supported and will yield parse errors: + - bin 8 - bin 32 (0xC4..0xC6) + - ext 8 - ext 32 (0xC7..0xC9) + - fixext 1 - fixext 16 (0xD4..0xD8) + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @param[in] i an input in MessagePack format convertible to an input + adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from MessagePack were + used in the given input @a i or if the input is not valid MessagePack + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + MessagePack format to a JSON value.,from_msgpack} + + @sa http://msgpack.org + @sa @ref to_msgpack(const basic_json&) for the analogous serialization + @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR + format + @sa @ref from_ubjson(detail::input_adapter, const bool) for the related + UBJSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 + */ + static basic_json from_msgpack(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_msgpack(strict); + } + + /*! + @copydoc from_msgpack(detail::input_adapter, const bool) + */ + template::value, int> = 0> + static basic_json from_msgpack(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_msgpack(strict); + } + + /*! + @brief create a JSON value from an input in UBJSON format + + Deserializes a given input @a i to a JSON value using the UBJSON (Universal + Binary JSON) serialization format. + + The library maps UBJSON types to JSON value types as follows: + + UBJSON type | JSON value type | marker + ----------- | --------------------------------------- | ------ + no-op | *no value, next value is read* | `N` + null | `null` | `Z` + false | `false` | `F` + true | `true` | `T` + float32 | number_float | `d` + float64 | number_float | `D` + uint8 | number_unsigned | `U` + int8 | number_integer | `i` + int16 | number_integer | `I` + int32 | number_integer | `l` + int64 | number_integer | `L` + string | string | `S` + char | string | `C` + array | array (optimized values are supported) | `[` + object | object (optimized values are supported) | `{` + + @note The mapping is **complete** in the sense that any UBJSON value can + be converted to a JSON value. + + @param[in] i an input in UBJSON format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if a parse error occurs + @throw parse_error.113 if a string could not be parsed successfully + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + UBJSON format to a JSON value.,from_ubjson} + + @sa http://ubjson.org + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + analogous serialization + @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR + format + @sa @ref from_msgpack(detail::input_adapter, const bool) for the related + MessagePack format + + @since version 3.1.0 + */ + static basic_json from_ubjson(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_ubjson(strict); + } + + template::value, int> = 0> + static basic_json from_ubjson(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_ubjson(strict); + } + + /// @} + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer} + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitive values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this function, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw parse_error.104 if the JSON patch does not consist of an array of + objects + + @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @throw out_of_range.401 if an array index is out of range. + + @throw out_of_range.403 if a JSON pointer inside the patch could not be + resolved successfully in the current JSON value; example: `"key baz not + found"` + + @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", + "move") + + @throw other_error.501 if "test" operation was unsuccessful + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string & op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.is_root()) + { + result = val; + } + else + { + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = json_pointer::array_index(last_path); + if (JSON_UNLIKELY(static_cast(idx) > parent.size())) + { + // avoid undefined behavior + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + else + { + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + } + break; + } + + default: + { + // if there exists a parent it cannot be primitive + assert(false); // LCOV_EXCL_LINE + } + } + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (JSON_LIKELY(it != parent.end())) + { + parent.erase(it); + } + else + { + JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found")); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(static_cast(json_pointer::array_index(last_path))); + } + }; + + // type check: top level value must be an array + if (JSON_UNLIKELY(not json_patch.is_array())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // iterate and apply the operations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json & + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (JSON_UNLIKELY(it == val.m_value.object->end())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'")); + } + + // check if result is of type string + if (JSON_UNLIKELY(string_type and not it->second.is_string())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'")); + } + + // no error: return value + return it->second; + }; + + // type check: every element of the array must be an object + if (JSON_UNLIKELY(not val.is_object())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // collect mandatory members + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const std::string from_path = get_value("copy", "from", true); + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The copy is functionally identical to an "add" + // operation at the target location using the value + // specified in the "from" member. + operation_add(ptr, v); + break; + } + + case patch_operations::test: + { + bool success = false; + JSON_TRY + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_CATCH (out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (JSON_UNLIKELY(not success)) + { + JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump())); + } + + break; + } + + case patch_operations::invalid: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid")); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to compare from + @param[in] target JSON value to compare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + @sa @ref merge_patch -- apply a JSON Merge Patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + static basic_json diff(const basic_json& source, const basic_json& target, + const std::string& path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + } + else + { + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + std::size_t i = 0; + while (i < source.size() and i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + std::to_string(i)}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.cbegin(); it != source.cend(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.cbegin(); it != target.cend(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + break; + } + } + } + + return result; + } + + /// @} + + //////////////////////////////// + // JSON Merge Patch functions // + //////////////////////////////// + + /// @name JSON Merge Patch functions + /// @{ + + /*! + @brief applies a JSON Merge Patch + + The merge patch format is primarily intended for use with the HTTP PATCH + method as a means of describing a set of modifications to a target + resource's content. This function applies a merge patch to the current + JSON value. + + The function implements the following algorithm from Section 2 of + [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396): + + ``` + define MergePatch(Target, Patch): + if Patch is an Object: + if Target is not an Object: + Target = {} // Ignore the contents and set it to an empty Object + for each Name/Value pair in Patch: + if Value is null: + if Name exists in Target: + remove the Name/Value pair from Target + else: + Target[Name] = MergePatch(Target[Name], Value) + return Target + else: + return Patch + ``` + + Thereby, `Target` is the current object; that is, the patch is applied to + the current value. + + @param[in] patch the patch to apply + + @complexity Linear in the lengths of @a patch. + + @liveexample{The following code shows how a JSON Merge Patch is applied to + a JSON document.,merge_patch} + + @sa @ref patch -- apply a JSON patch + @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) + + @since version 3.0.0 + */ + void merge_patch(const basic_json& patch) + { + if (patch.is_object()) + { + if (not is_object()) + { + *this = object(); + } + for (auto it = patch.begin(); it != patch.end(); ++it) + { + if (it.value().is_null()) + { + erase(it.key()); + } + else + { + operator[](it.key()).merge_patch(it.value()); + } + } + } + else + { + *this = patch; + } + } + + /// @} +}; +} // namespace nlohmann + +/////////////////////// +// nonmember support // +/////////////////////// + +// specialization of std::swap, and std::hash +namespace std +{ +/*! +@brief exchanges the values of two JSON objects + +@since version 1.0.0 +*/ +template<> +inline void swap(nlohmann::json& j1, + nlohmann::json& j2) noexcept( + is_nothrow_move_constructible::value and + is_nothrow_move_assignable::value + ) +{ + j1.swap(j2); +} + +/// hash value for JSON objects +template<> +struct hash +{ + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ + std::size_t operator()(const nlohmann::json& j) const + { + // a naive hashing via the string representation + const auto& h = hash(); + return h(j.dump()); + } +}; + +/// specialization for std::less +/// @note: do not remove the space after '<', +/// see https://github.com/nlohmann/json/pull/679 +template<> +struct less< ::nlohmann::detail::value_t> +{ + /*! + @brief compare two value_t enum values + @since version 3.0.0 + */ + bool operator()(nlohmann::detail::value_t lhs, + nlohmann::detail::value_t rhs) const noexcept + { + return nlohmann::detail::operator<(lhs, rhs); + } +}; + +} // namespace std + +/*! +@brief user-defined string literal for JSON values + +This operator implements a user-defined string literal for JSON objects. It +can be used by adding `"_json"` to a string literal and returns a JSON object +if no parse error occurred. + +@param[in] s a string representation of a JSON object +@param[in] n the length of string @a s +@return a JSON object + +@since version 1.0.0 +*/ +inline nlohmann::json operator "" _json(const char* s, std::size_t n) +{ + return nlohmann::json::parse(s, s + n); +} + +/*! +@brief user-defined string literal for JSON pointer + +This operator implements a user-defined string literal for JSON Pointers. It +can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer +object if no parse error occurred. + +@param[in] s a string representation of a JSON Pointer +@param[in] n the length of string @a s +@return a JSON pointer object + +@since version 2.0.0 +*/ +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) +{ + return nlohmann::json::json_pointer(std::string(s, n)); +} + +// #include + + +// restore GCC/clang diagnostic settings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic pop +#endif +#if defined(__clang__) + #pragma GCC diagnostic pop +#endif + +// clean up +#undef JSON_CATCH +#undef JSON_THROW +#undef JSON_TRY +#undef JSON_LIKELY +#undef JSON_UNLIKELY +#undef JSON_DEPRECATED +#undef JSON_HAS_CPP_14 +#undef JSON_HAS_CPP_17 +#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION +#undef NLOHMANN_BASIC_JSON_TPL +#undef NLOHMANN_JSON_HAS_HELPER + + +#endif From 3b4a6d292c985dfe622aa1ac92631702a81cf343 Mon Sep 17 00:00:00 2001 From: Tony Kozlovsky Date: Sat, 8 Sep 2018 15:08:16 +0300 Subject: [PATCH 31/96] add cpp17chipmunk example --- .../chipmunk_src/include/chipmunk/chipmunk.h" | 231 +++++ .../include/chipmunk/chipmunk_ffi.h" | 105 ++ .../include/chipmunk/chipmunk_private.h" | 344 +++++++ .../include/chipmunk/chipmunk_structs.h" | 450 +++++++++ .../include/chipmunk/chipmunk_types.h" | 268 ++++++ .../include/chipmunk/chipmunk_unsafe.h" | 66 ++ .../include/chipmunk/cpArbiter.h" | 145 +++ .../chipmunk_src/include/chipmunk/cpBB.h" | 183 ++++ .../chipmunk_src/include/chipmunk/cpBody.h" | 189 ++++ .../include/chipmunk/cpConstraint.h" | 95 ++ .../include/chipmunk/cpDampedRotarySpring.h" | 58 ++ .../include/chipmunk/cpDampedSpring.h" | 68 ++ .../include/chipmunk/cpGearJoint.h" | 45 + .../include/chipmunk/cpGrooveJoint.h" | 50 + .../include/chipmunk/cpHastySpace.h" | 27 + .../chipmunk_src/include/chipmunk/cpMarch.h" | 28 + .../include/chipmunk/cpPinJoint.h" | 50 + .../include/chipmunk/cpPivotJoint.h" | 47 + .../include/chipmunk/cpPolyShape.h" | 56 ++ .../include/chipmunk/cpPolyline.h" | 70 ++ .../include/chipmunk/cpRatchetJoint.h" | 50 + .../chipmunk_src/include/chipmunk/cpRobust.h" | 11 + .../include/chipmunk/cpRotaryLimitJoint.h" | 45 + .../chipmunk_src/include/chipmunk/cpShape.h" | 199 ++++ .../include/chipmunk/cpSimpleMotor.h" | 43 + .../include/chipmunk/cpSlideJoint.h" | 55 ++ .../chipmunk_src/include/chipmunk/cpSpace.h" | 319 +++++++ .../include/chipmunk/cpSpatialIndex.h" | 227 +++++ .../include/chipmunk/cpTransform.h" | 198 ++++ .../chipmunk_src/include/chipmunk/cpVect.h" | 230 +++++ .../chipmunk_src/src/chipmunk.c" | 331 +++++++ .../chipmunk_src/src/cpArbiter.c" | 496 ++++++++++ .../chipmunk_src/src/cpArray.c" | 101 ++ .../chipmunk_src/src/cpBBTree.c" | 896 ++++++++++++++++++ .../chipmunk_src/src/cpBody.c" | 626 ++++++++++++ .../chipmunk_src/src/cpCollision.c" | 726 ++++++++++++++ .../chipmunk_src/src/cpConstraint.c" | 173 ++++ .../chipmunk_src/src/cpDampedRotarySpring.c" | 178 ++++ .../chipmunk_src/src/cpDampedSpring.c" | 216 +++++ .../chipmunk_src/src/cpGearJoint.c" | 145 +++ .../chipmunk_src/src/cpGrooveJoint.c" | 197 ++++ .../chipmunk_src/src/cpHashSet.c" | 253 +++++ .../chipmunk_src/src/cpHastySpace.c" | 497 ++++++++++ .../chipmunk_src/src/cpMarch.c" | 157 +++ .../chipmunk_src/src/cpPinJoint.c" | 172 ++++ .../chipmunk_src/src/cpPivotJoint.c" | 152 +++ .../chipmunk_src/src/cpPolyShape.c" | 323 +++++++ .../chipmunk_src/src/cpPolyline.c" | 652 +++++++++++++ .../chipmunk_src/src/cpRatchetJoint.c" | 179 ++++ .../chipmunk_src/src/cpRobust.c" | 13 + .../chipmunk_src/src/cpRotaryLimitJoint.c" | 160 ++++ .../chipmunk_src/src/cpShape.c" | 603 ++++++++++++ .../chipmunk_src/src/cpSimpleMotor.c" | 123 +++ .../chipmunk_src/src/cpSlideJoint.c" | 195 ++++ .../chipmunk_src/src/cpSpace.c" | 700 ++++++++++++++ .../chipmunk_src/src/cpSpaceComponent.c" | 349 +++++++ .../chipmunk_src/src/cpSpaceDebug.c" | 189 ++++ .../chipmunk_src/src/cpSpaceHash.c" | 634 +++++++++++++ .../chipmunk_src/src/cpSpaceQuery.c" | 246 +++++ .../chipmunk_src/src/cpSpaceStep.c" | 445 +++++++++ .../chipmunk_src/src/cpSpatialIndex.c" | 69 ++ .../chipmunk_src/src/cpSweep1D.c" | 254 +++++ .../chipmunk_src/src/prime.h" | 68 ++ .../solution/strategy.cpp" | 103 ++ 64 files changed, 14573 insertions(+) create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_ffi.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_private.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_structs.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_types.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_unsafe.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpArbiter.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBB.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBody.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpConstraint.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedRotarySpring.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedSpring.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGearJoint.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGrooveJoint.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpHastySpace.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpMarch.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPinJoint.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPivotJoint.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyShape.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyline.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRatchetJoint.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRobust.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRotaryLimitJoint.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpShape.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSimpleMotor.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSlideJoint.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpace.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpatialIndex.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpTransform.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpVect.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/chipmunk.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArbiter.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArray.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBBTree.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBody.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpCollision.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpConstraint.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedRotarySpring.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedSpring.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGearJoint.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGrooveJoint.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHashSet.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHastySpace.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpMarch.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPinJoint.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPivotJoint.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyShape.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyline.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRatchetJoint.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRobust.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRotaryLimitJoint.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpShape.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSimpleMotor.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSlideJoint.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpace.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceComponent.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceDebug.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceHash.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceQuery.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceStep.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpatialIndex.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSweep1D.c" create mode 100644 "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/prime.h" create mode 100644 "madcars/examples/\321\201++17chipmunk/solution/strategy.cpp" diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk.h" new file mode 100644 index 0000000..0f7a7b5 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk.h" @@ -0,0 +1,231 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#ifndef CHIPMUNK_H +#define CHIPMUNK_H + +#include +#include + +#ifdef WIN32 + // For alloca(). + #include + //Pymunk on windows support + //#define CP_EXPORT __declspec(dllexport) + #define CP_EXPORT +#else + #include + #define CP_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +CP_EXPORT void cpMessage(const char *condition, const char *file, int line, int isError, int isHardError, const char *message, ...); +#ifdef NDEBUG + #define cpAssertWarn(__condition__, ...) + #define cpAssertSoft(__condition__, ...) +#else + #define cpAssertSoft(__condition__, ...) if(!(__condition__)){cpMessage(#__condition__, __FILE__, __LINE__, 1, 0, __VA_ARGS__); abort();} + #define cpAssertWarn(__condition__, ...) if(!(__condition__)) cpMessage(#__condition__, __FILE__, __LINE__, 0, 0, __VA_ARGS__) +#endif + +// Hard assertions are used in situations where the program definitely will crash anyway, and the reason is inexpensive to detect. +#define cpAssertHard(__condition__, ...) if(!(__condition__)){cpMessage(#__condition__, __FILE__, __LINE__, 1, 1, __VA_ARGS__); abort();} + +#include "chipmunk_types.h" + +/// @defgroup misc Misc +/// @{ + +/// Allocated size for various Chipmunk buffers +#ifndef CP_BUFFER_BYTES + #define CP_BUFFER_BYTES (32*1024) +#endif + +#ifndef cpcalloc + /// Chipmunk calloc() alias. + #define cpcalloc calloc +#endif + +#ifndef cprealloc + /// Chipmunk realloc() alias. + #define cprealloc realloc +#endif + +#ifndef cpfree + /// Chipmunk free() alias. + #define cpfree free +#endif + +typedef struct cpArray cpArray; +typedef struct cpHashSet cpHashSet; + +typedef struct cpBody cpBody; + +typedef struct cpShape cpShape; +typedef struct cpCircleShape cpCircleShape; +typedef struct cpSegmentShape cpSegmentShape; +typedef struct cpPolyShape cpPolyShape; + +typedef struct cpConstraint cpConstraint; +typedef struct cpPinJoint cpPinJoint; +typedef struct cpSlideJoint cpSlideJoint; +typedef struct cpPivotJoint cpPivotJoint; +typedef struct cpGrooveJoint cpGrooveJoint; +typedef struct cpDampedSpring cpDampedSpring; +typedef struct cpDampedRotarySpring cpDampedRotarySpring; +typedef struct cpRotaryLimitJoint cpRotaryLimitJoint; +typedef struct cpRatchetJoint cpRatchetJoint; +typedef struct cpGearJoint cpGearJoint; +typedef struct cpSimpleMotorJoint cpSimpleMotorJoint; + +typedef struct cpCollisionHandler cpCollisionHandler; +typedef struct cpContactPointSet cpContactPointSet; +typedef struct cpArbiter cpArbiter; + +typedef struct cpSpace cpSpace; + +#include "cpVect.h" +#include "cpBB.h" +#include "cpTransform.h" +#include "cpSpatialIndex.h" + +#include "cpArbiter.h" + +#include "cpBody.h" +#include "cpShape.h" +#include "cpPolyShape.h" + +#include "cpConstraint.h" + +#include "cpSpace.h" + +// Chipmunk 7.0.1 +#define CP_VERSION_MAJOR 7 +#define CP_VERSION_MINOR 0 +#define CP_VERSION_RELEASE 1 + +/// Version string. +CP_EXPORT extern const char *cpVersionString; + +/// Calculate the moment of inertia for a circle. +/// @c r1 and @c r2 are the inner and outer diameters. A solid circle has an inner diameter of 0. +CP_EXPORT cpFloat cpMomentForCircle(cpFloat m, cpFloat r1, cpFloat r2, cpVect offset); + +/// Calculate area of a hollow circle. +/// @c r1 and @c r2 are the inner and outer diameters. A solid circle has an inner diameter of 0. +CP_EXPORT cpFloat cpAreaForCircle(cpFloat r1, cpFloat r2); + +/// Calculate the moment of inertia for a line segment. +/// Beveling radius is not supported. +CP_EXPORT cpFloat cpMomentForSegment(cpFloat m, cpVect a, cpVect b, cpFloat radius); + +/// Calculate the area of a fattened (capsule shaped) line segment. +CP_EXPORT cpFloat cpAreaForSegment(cpVect a, cpVect b, cpFloat radius); + +/// Calculate the moment of inertia for a solid polygon shape assuming it's center of gravity is at it's centroid. The offset is added to each vertex. +CP_EXPORT cpFloat cpMomentForPoly(cpFloat m, int count, const cpVect *verts, cpVect offset, cpFloat radius); + +/// Calculate the signed area of a polygon. A Clockwise winding gives positive area. +/// This is probably backwards from what you expect, but matches Chipmunk's the winding for poly shapes. +CP_EXPORT cpFloat cpAreaForPoly(const int count, const cpVect *verts, cpFloat radius); + +/// Calculate the natural centroid of a polygon. +CP_EXPORT cpVect cpCentroidForPoly(const int count, const cpVect *verts); + +/// Calculate the moment of inertia for a solid box. +CP_EXPORT cpFloat cpMomentForBox(cpFloat m, cpFloat width, cpFloat height); + +/// Calculate the moment of inertia for a solid box. +CP_EXPORT cpFloat cpMomentForBox2(cpFloat m, cpBB box); + +/// Calculate the convex hull of a given set of points. Returns the count of points in the hull. +/// @c result must be a pointer to a @c cpVect array with at least @c count elements. If @c verts == @c result, then @c verts will be reduced inplace. +/// @c first is an optional pointer to an integer to store where the first vertex in the hull came from (i.e. verts[first] == result[0]) +/// @c tol is the allowed amount to shrink the hull when simplifying it. A tolerance of 0.0 creates an exact hull. +CP_EXPORT int cpConvexHull(int count, const cpVect *verts, cpVect *result, int *first, cpFloat tol); + +#ifdef _MSC_VER +#include "malloc.h" +#endif + +/// Convenience macro to work with cpConvexHull. +/// @c count and @c verts is the input array passed to cpConvexHull(). +/// @c count_var and @c verts_var are the names of the variables the macro creates to store the result. +/// The output vertex array is allocated on the stack using alloca() so it will be freed automatically, but cannot be returned from the current scope. +#define CP_CONVEX_HULL(__count__, __verts__, __count_var__, __verts_var__) \ +cpVect *__verts_var__ = (cpVect *)alloca(__count__*sizeof(cpVect)); \ +int __count_var__ = cpConvexHull(__count__, __verts__, __verts_var__, NULL, 0.0); \ + +/// Returns the closest point on the line segment ab, to the point p. +static inline cpVect +cpClosetPointOnSegment(const cpVect p, const cpVect a, const cpVect b) +{ + cpVect delta = cpvsub(a, b); + cpFloat t = cpfclamp01(cpvdot(delta, cpvsub(p, b))/cpvlengthsq(delta)); + return cpvadd(b, cpvmult(delta, t)); +} + +#if defined(__has_extension) +#if __has_extension(blocks) +// Define alternate block based alternatives for a few of the callback heavy functions. +// Collision handlers are post-step callbacks are not included to avoid memory management issues. +// If you want to use blocks for those and are aware of how to correctly manage the memory, the implementation is trivial. + +void cpSpaceEachBody_b(cpSpace *space, void (^block)(cpBody *body)); +void cpSpaceEachShape_b(cpSpace *space, void (^block)(cpShape *shape)); +void cpSpaceEachConstraint_b(cpSpace *space, void (^block)(cpConstraint *constraint)); + +void cpBodyEachShape_b(cpBody *body, void (^block)(cpShape *shape)); +void cpBodyEachConstraint_b(cpBody *body, void (^block)(cpConstraint *constraint)); +void cpBodyEachArbiter_b(cpBody *body, void (^block)(cpArbiter *arbiter)); + +typedef void (^cpSpacePointQueryBlock)(cpShape *shape, cpVect point, cpFloat distance, cpVect gradient); +void cpSpacePointQuery_b(cpSpace *space, cpVect point, cpFloat maxDistance, cpShapeFilter filter, cpSpacePointQueryBlock block); + +typedef void (^cpSpaceSegmentQueryBlock)(cpShape *shape, cpVect point, cpVect normal, cpFloat alpha); +void cpSpaceSegmentQuery_b(cpSpace *space, cpVect start, cpVect end, cpFloat radius, cpShapeFilter filter, cpSpaceSegmentQueryBlock block); + +typedef void (^cpSpaceBBQueryBlock)(cpShape *shape); +void cpSpaceBBQuery_b(cpSpace *space, cpBB bb, cpShapeFilter filter, cpSpaceBBQueryBlock block); + +typedef void (^cpSpaceShapeQueryBlock)(cpShape *shape, cpContactPointSet *points); +cpBool cpSpaceShapeQuery_b(cpSpace *space, cpShape *shape, cpSpaceShapeQueryBlock block); + +#endif +#endif + + +//@} + +#ifdef __cplusplus +} + +static inline cpVect operator *(const cpVect v, const cpFloat s){return cpvmult(v, s);} +static inline cpVect operator +(const cpVect v1, const cpVect v2){return cpvadd(v1, v2);} +static inline cpVect operator -(const cpVect v1, const cpVect v2){return cpvsub(v1, v2);} +static inline cpBool operator ==(const cpVect v1, const cpVect v2){return cpveql(v1, v2);} +static inline cpVect operator -(const cpVect v){return cpvneg(v);} + +#endif +#endif diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_ffi.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_ffi.h" new file mode 100644 index 0000000..86e3d9f --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_ffi.h" @@ -0,0 +1,105 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#ifdef CHIPMUNK_FFI + +// Create non static inlined copies of Chipmunk functions, useful for working with dynamic FFIs +// For many languages, it may be faster to reimplement these functions natively instead. +// Note: This file should only be included by chipmunk.c. + +#ifdef _MSC_VER + #if _MSC_VER >= 1600 + #define MAKE_REF(name) CP_EXPORT decltype(name) *_##name = name + #else + #define MAKE_REF(name) + #endif +#else + #define MAKE_REF(name) __typeof__(name) *_##name = name +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +MAKE_REF(cpv); // makes a variable named _cpv that contains the function pointer for cpv() +MAKE_REF(cpveql); +MAKE_REF(cpvadd); +MAKE_REF(cpvneg); +MAKE_REF(cpvsub); +MAKE_REF(cpvmult); +MAKE_REF(cpvdot); +MAKE_REF(cpvcross); +MAKE_REF(cpvperp); +MAKE_REF(cpvrperp); +MAKE_REF(cpvproject); +MAKE_REF(cpvforangle); +MAKE_REF(cpvtoangle); +MAKE_REF(cpvrotate); +MAKE_REF(cpvunrotate); +MAKE_REF(cpvlengthsq); +MAKE_REF(cpvlength); +MAKE_REF(cpvlerp); +MAKE_REF(cpvnormalize); +MAKE_REF(cpvclamp); +MAKE_REF(cpvlerpconst); +MAKE_REF(cpvdist); +MAKE_REF(cpvdistsq); +MAKE_REF(cpvnear); + +MAKE_REF(cpfmax); +MAKE_REF(cpfmin); +MAKE_REF(cpfabs); +MAKE_REF(cpfclamp); +MAKE_REF(cpflerp); +MAKE_REF(cpflerpconst); + +MAKE_REF(cpBBNew); +MAKE_REF(cpBBNewForExtents); +MAKE_REF(cpBBNewForCircle); +MAKE_REF(cpBBIntersects); +MAKE_REF(cpBBContainsBB); +MAKE_REF(cpBBContainsVect); +MAKE_REF(cpBBMerge); +MAKE_REF(cpBBExpand); +MAKE_REF(cpBBCenter); +MAKE_REF(cpBBArea); +MAKE_REF(cpBBMergedArea); +MAKE_REF(cpBBSegmentQuery); +MAKE_REF(cpBBIntersectsSegment); +MAKE_REF(cpBBClampVect); + +MAKE_REF(cpSpatialIndexDestroy); +MAKE_REF(cpSpatialIndexCount); +MAKE_REF(cpSpatialIndexEach); +MAKE_REF(cpSpatialIndexContains); +MAKE_REF(cpSpatialIndexInsert); +MAKE_REF(cpSpatialIndexRemove); +MAKE_REF(cpSpatialIndexReindex); +MAKE_REF(cpSpatialIndexReindexObject); +MAKE_REF(cpSpatialIndexSegmentQuery); +MAKE_REF(cpSpatialIndexQuery); +MAKE_REF(cpSpatialIndexReindexQuery); + +#ifdef __cplusplus +} +#endif + +#endif diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_private.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_private.h" new file mode 100644 index 0000000..a27fdec --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_private.h" @@ -0,0 +1,344 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#ifndef CHIPMUNK_PRIVATE_H +#define CHIPMUNK_PRIVATE_H + +#include "chipmunk/chipmunk.h" +#include "chipmunk/chipmunk_structs.h" + +#define CP_HASH_COEF (3344921057ul) +#define CP_HASH_PAIR(A, B) ((cpHashValue)(A)*CP_HASH_COEF ^ (cpHashValue)(B)*CP_HASH_COEF) + +// TODO: Eww. Magic numbers. +#define MAGIC_EPSILON 1e-5 + + +//MARK: cpArray + +cpArray *cpArrayNew(int size); + +void cpArrayFree(cpArray *arr); + +void cpArrayPush(cpArray *arr, void *object); +void *cpArrayPop(cpArray *arr); +void cpArrayDeleteObj(cpArray *arr, void *obj); +cpBool cpArrayContains(cpArray *arr, void *ptr); + +void cpArrayFreeEach(cpArray *arr, void (freeFunc)(void*)); + + +//MARK: cpHashSet + +typedef cpBool (*cpHashSetEqlFunc)(void *ptr, void *elt); +typedef void *(*cpHashSetTransFunc)(void *ptr, void *data); + +cpHashSet *cpHashSetNew(int size, cpHashSetEqlFunc eqlFunc); +void cpHashSetSetDefaultValue(cpHashSet *set, void *default_value); + +void cpHashSetFree(cpHashSet *set); + +int cpHashSetCount(cpHashSet *set); +void *cpHashSetInsert(cpHashSet *set, cpHashValue hash, void *ptr, cpHashSetTransFunc trans, void *data); +void *cpHashSetRemove(cpHashSet *set, cpHashValue hash, void *ptr); +void *cpHashSetFind(cpHashSet *set, cpHashValue hash, void *ptr); + +typedef void (*cpHashSetIteratorFunc)(void *elt, void *data); +void cpHashSetEach(cpHashSet *set, cpHashSetIteratorFunc func, void *data); + +typedef cpBool (*cpHashSetFilterFunc)(void *elt, void *data); +void cpHashSetFilter(cpHashSet *set, cpHashSetFilterFunc func, void *data); + + +//MARK: Bodies + +void cpBodyAddShape(cpBody *body, cpShape *shape); +void cpBodyRemoveShape(cpBody *body, cpShape *shape); + +//void cpBodyAccumulateMassForShape(cpBody *body, cpShape *shape); +void cpBodyAccumulateMassFromShapes(cpBody *body); + +void cpBodyRemoveConstraint(cpBody *body, cpConstraint *constraint); + + +//MARK: Spatial Index Functions + +cpSpatialIndex *cpSpatialIndexInit(cpSpatialIndex *index, cpSpatialIndexClass *klass, cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex); + + +//MARK: Arbiters + +cpArbiter* cpArbiterInit(cpArbiter *arb, cpShape *a, cpShape *b); + +static inline struct cpArbiterThread * +cpArbiterThreadForBody(cpArbiter *arb, cpBody *body) +{ + return (arb->body_a == body ? &arb->thread_a : &arb->thread_b); +} + +void cpArbiterUnthread(cpArbiter *arb); + +void cpArbiterUpdate(cpArbiter *arb, struct cpCollisionInfo *info, cpSpace *space); +void cpArbiterPreStep(cpArbiter *arb, cpFloat dt, cpFloat bias, cpFloat slop); +void cpArbiterApplyCachedImpulse(cpArbiter *arb, cpFloat dt_coef); +void cpArbiterApplyImpulse(cpArbiter *arb); + + +//MARK: Shapes/Collisions + +cpShape *cpShapeInit(cpShape *shape, const cpShapeClass *klass, cpBody *body, struct cpShapeMassInfo massInfo); + +static inline cpBool +cpShapeActive(cpShape *shape) +{ + // checks if the shape is added to a shape list. + // TODO could this just check the space now? + return (shape->prev || (shape->body && shape->body->shapeList == shape)); +} + +// Note: This function returns contact points with r1/r2 in absolute coordinates, not body relative. +struct cpCollisionInfo cpCollide(const cpShape *a, const cpShape *b, cpCollisionID id, struct cpContact *contacts); + +static inline void +CircleSegmentQuery(cpShape *shape, cpVect center, cpFloat r1, cpVect a, cpVect b, cpFloat r2, cpSegmentQueryInfo *info) +{ + cpVect da = cpvsub(a, center); + cpVect db = cpvsub(b, center); + cpFloat rsum = r1 + r2; + + cpFloat qa = cpvdot(da, da) - 2.0f*cpvdot(da, db) + cpvdot(db, db); + cpFloat qb = cpvdot(da, db) - cpvdot(da, da); + cpFloat det = qb*qb - qa*(cpvdot(da, da) - rsum*rsum); + + if(det >= 0.0f){ + cpFloat t = (-qb - cpfsqrt(det))/(qa); + if(0.0f<= t && t <= 1.0f){ + cpVect n = cpvnormalize(cpvlerp(da, db, t)); + + info->shape = shape; + info->point = cpvsub(cpvlerp(a, b, t), cpvmult(n, r2)); + info->normal = n; + info->alpha = t; + } + } +} + +static inline cpBool +cpShapeFilterReject(cpShapeFilter a, cpShapeFilter b) +{ + // Reject the collision if: + return ( + // They are in the same non-zero group. + (a.group != 0 && a.group == b.group) || + // One of the category/mask combinations fails. + (a.categories & b.mask) == 0 || + (b.categories & a.mask) == 0 + ); +} + +void cpLoopIndexes(const cpVect *verts, int count, int *start, int *end); + + +//MARK: Constraints +// TODO naming conventions here + +void cpConstraintInit(cpConstraint *constraint, const struct cpConstraintClass *klass, cpBody *a, cpBody *b); + +static inline void +cpConstraintActivateBodies(cpConstraint *constraint) +{ + cpBody *a = constraint->a; cpBodyActivate(a); + cpBody *b = constraint->b; cpBodyActivate(b); +} + +static inline cpVect +relative_velocity(cpBody *a, cpBody *b, cpVect r1, cpVect r2){ + cpVect v1_sum = cpvadd(a->v, cpvmult(cpvperp(r1), a->w)); + cpVect v2_sum = cpvadd(b->v, cpvmult(cpvperp(r2), b->w)); + + return cpvsub(v2_sum, v1_sum); +} + +static inline cpFloat +normal_relative_velocity(cpBody *a, cpBody *b, cpVect r1, cpVect r2, cpVect n){ + return cpvdot(relative_velocity(a, b, r1, r2), n); +} + +static inline void +apply_impulse(cpBody *body, cpVect j, cpVect r){ + body->v = cpvadd(body->v, cpvmult(j, body->m_inv)); + body->w += body->i_inv*cpvcross(r, j); +} + +static inline void +apply_impulses(cpBody *a , cpBody *b, cpVect r1, cpVect r2, cpVect j) +{ + apply_impulse(a, cpvneg(j), r1); + apply_impulse(b, j, r2); +} + +static inline void +apply_bias_impulse(cpBody *body, cpVect j, cpVect r) +{ + body->v_bias = cpvadd(body->v_bias, cpvmult(j, body->m_inv)); + body->w_bias += body->i_inv*cpvcross(r, j); +} + +static inline void +apply_bias_impulses(cpBody *a , cpBody *b, cpVect r1, cpVect r2, cpVect j) +{ + apply_bias_impulse(a, cpvneg(j), r1); + apply_bias_impulse(b, j, r2); +} + +static inline cpFloat +k_scalar_body(cpBody *body, cpVect r, cpVect n) +{ + cpFloat rcn = cpvcross(r, n); + return body->m_inv + body->i_inv*rcn*rcn; +} + +static inline cpFloat +k_scalar(cpBody *a, cpBody *b, cpVect r1, cpVect r2, cpVect n) +{ + cpFloat value = k_scalar_body(a, r1, n) + k_scalar_body(b, r2, n); + cpAssertSoft(value != 0.0, "Unsolvable collision or constraint."); + + return value; +} + +static inline cpMat2x2 +k_tensor(cpBody *a, cpBody *b, cpVect r1, cpVect r2) +{ + cpFloat m_sum = a->m_inv + b->m_inv; + + // start with Identity*m_sum + cpFloat k11 = m_sum, k12 = 0.0f; + cpFloat k21 = 0.0f, k22 = m_sum; + + // add the influence from r1 + cpFloat a_i_inv = a->i_inv; + cpFloat r1xsq = r1.x * r1.x * a_i_inv; + cpFloat r1ysq = r1.y * r1.y * a_i_inv; + cpFloat r1nxy = -r1.x * r1.y * a_i_inv; + k11 += r1ysq; k12 += r1nxy; + k21 += r1nxy; k22 += r1xsq; + + // add the influnce from r2 + cpFloat b_i_inv = b->i_inv; + cpFloat r2xsq = r2.x * r2.x * b_i_inv; + cpFloat r2ysq = r2.y * r2.y * b_i_inv; + cpFloat r2nxy = -r2.x * r2.y * b_i_inv; + k11 += r2ysq; k12 += r2nxy; + k21 += r2nxy; k22 += r2xsq; + + // invert + cpFloat det = k11*k22 - k12*k21; + cpAssertSoft(det != 0.0, "Unsolvable constraint."); + + cpFloat det_inv = 1.0f/det; + return cpMat2x2New( + k22*det_inv, -k12*det_inv, + -k21*det_inv, k11*det_inv + ); +} + +static inline cpFloat +bias_coef(cpFloat errorBias, cpFloat dt) +{ + return 1.0f - cpfpow(errorBias, dt); +} + + +//MARK: Spaces + +#define cpAssertSpaceUnlocked(space) \ + cpAssertHard(!space->locked, \ + "This operation cannot be done safely during a call to cpSpaceStep() or during a query. " \ + "Put these calls into a post-step callback." \ + ); + +void cpSpaceSetStaticBody(cpSpace *space, cpBody *body); + +extern cpCollisionHandler cpCollisionHandlerDoNothing; + +void cpSpaceProcessComponents(cpSpace *space, cpFloat dt); + +void cpSpacePushFreshContactBuffer(cpSpace *space); +struct cpContact *cpContactBufferGetArray(cpSpace *space); +void cpSpacePushContacts(cpSpace *space, int count); + +cpPostStepCallback *cpSpaceGetPostStepCallback(cpSpace *space, void *key); + +cpBool cpSpaceArbiterSetFilter(cpArbiter *arb, cpSpace *space); +void cpSpaceFilterArbiters(cpSpace *space, cpBody *body, cpShape *filter); + +void cpSpaceActivateBody(cpSpace *space, cpBody *body); +void cpSpaceLock(cpSpace *space); +void cpSpaceUnlock(cpSpace *space, cpBool runPostStep); + +static inline void +cpSpaceUncacheArbiter(cpSpace *space, cpArbiter *arb) +{ + const cpShape *a = arb->a, *b = arb->b; + const cpShape *shape_pair[] = {a, b}; + cpHashValue arbHashID = CP_HASH_PAIR((cpHashValue)a, (cpHashValue)b); + cpHashSetRemove(space->cachedArbiters, arbHashID, shape_pair); + cpArrayDeleteObj(space->arbiters, arb); +} + +static inline cpArray * +cpSpaceArrayForBodyType(cpSpace *space, cpBodyType type) +{ + return (type == CP_BODY_TYPE_STATIC ? space->staticBodies : space->dynamicBodies); +} + +void cpShapeUpdateFunc(cpShape *shape, void *unused); +cpCollisionID cpSpaceCollideShapes(cpShape *a, cpShape *b, cpCollisionID id, cpSpace *space); + + +//MARK: Foreach loops + +static inline cpConstraint * +cpConstraintNext(cpConstraint *node, cpBody *body) +{ + return (node->a == body ? node->next_a : node->next_b); +} + +#define CP_BODY_FOREACH_CONSTRAINT(bdy, var)\ + for(cpConstraint *var = bdy->constraintList; var; var = cpConstraintNext(var, bdy)) + +static inline cpArbiter * +cpArbiterNext(cpArbiter *node, cpBody *body) +{ + return (node->body_a == body ? node->thread_a.next : node->thread_b.next); +} + +#define CP_BODY_FOREACH_ARBITER(bdy, var)\ + for(cpArbiter *var = bdy->arbiterList; var; var = cpArbiterNext(var, bdy)) + +#define CP_BODY_FOREACH_SHAPE(body, var)\ + for(cpShape *var = body->shapeList; var; var = var->next) + +#define CP_BODY_FOREACH_COMPONENT(root, var)\ + for(cpBody *var = root; var; var = var->sleeping.next) + +#endif diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_structs.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_structs.h" new file mode 100644 index 0000000..d8b1e6f --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_structs.h" @@ -0,0 +1,450 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +// All of the struct definitions for Chipmunk should be considered part of the private API. +// However, it is very valuable to know the struct sizes for preallocating memory. + +#ifndef CHIPMUNK_STRUCTS_H +#define CHIPMUNK_STRUCTS_H + +#include "chipmunk/chipmunk.h" + +struct cpArray { + int num, max; + void **arr; +}; + +struct cpBody { + // Integration functions + cpBodyVelocityFunc velocity_func; + cpBodyPositionFunc position_func; + + // mass and it's inverse + cpFloat m; + cpFloat m_inv; + + // moment of inertia and it's inverse + cpFloat i; + cpFloat i_inv; + + // center of gravity + cpVect cog; + + // position, velocity, force + cpVect p; + cpVect v; + cpVect f; + + // Angle, angular velocity, torque (radians) + cpFloat a; + cpFloat w; + cpFloat t; + + cpTransform transform; + + cpDataPointer userData; + + // "pseudo-velocities" used for eliminating overlap. + // Erin Catto has some papers that talk about what these are. + cpVect v_bias; + cpFloat w_bias; + + cpSpace *space; + + cpShape *shapeList; + cpArbiter *arbiterList; + cpConstraint *constraintList; + + struct { + cpBody *root; + cpBody *next; + cpFloat idleTime; + } sleeping; +}; + +enum cpArbiterState { + // Arbiter is active and its the first collision. + CP_ARBITER_STATE_FIRST_COLLISION, + // Arbiter is active and its not the first collision. + CP_ARBITER_STATE_NORMAL, + // Collision has been explicitly ignored. + // Either by returning false from a begin collision handler or calling cpArbiterIgnore(). + CP_ARBITER_STATE_IGNORE, + // Collison is no longer active. A space will cache an arbiter for up to cpSpace.collisionPersistence more steps. + CP_ARBITER_STATE_CACHED, + // Collison arbiter is invalid because one of the shapes was removed. + CP_ARBITER_STATE_INVALIDATED, +}; + +struct cpArbiterThread { + struct cpArbiter *next, *prev; +}; + +struct cpContact { + cpVect r1, r2; + + cpFloat nMass, tMass; + cpFloat bounce; // TODO: look for an alternate bounce solution. + + cpFloat jnAcc, jtAcc, jBias; + cpFloat bias; + + cpHashValue hash; +}; + +struct cpCollisionInfo { + const cpShape *a, *b; + cpCollisionID id; + + cpVect n; + + int count; + // TODO Should this be a unique struct type? + struct cpContact *arr; +}; + +struct cpArbiter { + cpFloat e; + cpFloat u; + cpVect surface_vr; + + cpDataPointer data; + + const cpShape *a, *b; + cpBody *body_a, *body_b; + struct cpArbiterThread thread_a, thread_b; + + int count; + struct cpContact *contacts; + cpVect n; + + // Regular, wildcard A and wildcard B collision handlers. + cpCollisionHandler *handler, *handlerA, *handlerB; + cpBool swapped; + + cpTimestamp stamp; + enum cpArbiterState state; +}; + +struct cpShapeMassInfo { + cpFloat m; + cpFloat i; + cpVect cog; + cpFloat area; +}; + +typedef enum cpShapeType{ + CP_CIRCLE_SHAPE, + CP_SEGMENT_SHAPE, + CP_POLY_SHAPE, + CP_NUM_SHAPES +} cpShapeType; + +typedef cpBB (*cpShapeCacheDataImpl)(cpShape *shape, cpTransform transform); +typedef void (*cpShapeDestroyImpl)(cpShape *shape); +typedef void (*cpShapePointQueryImpl)(const cpShape *shape, cpVect p, cpPointQueryInfo *info); +typedef void (*cpShapeSegmentQueryImpl)(const cpShape *shape, cpVect a, cpVect b, cpFloat radius, cpSegmentQueryInfo *info); + +typedef struct cpShapeClass cpShapeClass; + +struct cpShapeClass { + cpShapeType type; + + cpShapeCacheDataImpl cacheData; + cpShapeDestroyImpl destroy; + cpShapePointQueryImpl pointQuery; + cpShapeSegmentQueryImpl segmentQuery; +}; + +struct cpShape { + const cpShapeClass *klass; + + cpSpace *space; + cpBody *body; + struct cpShapeMassInfo massInfo; + cpBB bb; + + cpBool sensor; + + cpFloat e; + cpFloat u; + cpVect surfaceV; + + cpDataPointer userData; + + cpCollisionType type; + cpShapeFilter filter; + + cpShape *next; + cpShape *prev; + + cpHashValue hashid; +}; + +struct cpCircleShape { + cpShape shape; + + cpVect c, tc; + cpFloat r; +}; + +struct cpSegmentShape { + cpShape shape; + + cpVect a, b, n; + cpVect ta, tb, tn; + cpFloat r; + + cpVect a_tangent, b_tangent; +}; + +struct cpSplittingPlane { + cpVect v0, n; +}; + +#define CP_POLY_SHAPE_INLINE_ALLOC 6 + +struct cpPolyShape { + cpShape shape; + + cpFloat r; + + int count; + // The untransformed planes are appended at the end of the transformed planes. + struct cpSplittingPlane *planes; + + // Allocate a small number of splitting planes internally for simple poly. + struct cpSplittingPlane _planes[2*CP_POLY_SHAPE_INLINE_ALLOC]; +}; + +typedef void (*cpConstraintPreStepImpl)(cpConstraint *constraint, cpFloat dt); +typedef void (*cpConstraintApplyCachedImpulseImpl)(cpConstraint *constraint, cpFloat dt_coef); +typedef void (*cpConstraintApplyImpulseImpl)(cpConstraint *constraint, cpFloat dt); +typedef cpFloat (*cpConstraintGetImpulseImpl)(cpConstraint *constraint); + +typedef struct cpConstraintClass { + cpConstraintPreStepImpl preStep; + cpConstraintApplyCachedImpulseImpl applyCachedImpulse; + cpConstraintApplyImpulseImpl applyImpulse; + cpConstraintGetImpulseImpl getImpulse; +} cpConstraintClass; + +struct cpConstraint { + const cpConstraintClass *klass; + + cpSpace *space; + + cpBody *a, *b; + cpConstraint *next_a, *next_b; + + cpFloat maxForce; + cpFloat errorBias; + cpFloat maxBias; + + cpBool collideBodies; + + cpConstraintPreSolveFunc preSolve; + cpConstraintPostSolveFunc postSolve; + + cpDataPointer userData; +}; + +struct cpPinJoint { + cpConstraint constraint; + cpVect anchorA, anchorB; + cpFloat dist; + + cpVect r1, r2; + cpVect n; + cpFloat nMass; + + cpFloat jnAcc; + cpFloat bias; +}; + +struct cpSlideJoint { + cpConstraint constraint; + cpVect anchorA, anchorB; + cpFloat min, max; + + cpVect r1, r2; + cpVect n; + cpFloat nMass; + + cpFloat jnAcc; + cpFloat bias; +}; + +struct cpPivotJoint { + cpConstraint constraint; + cpVect anchorA, anchorB; + + cpVect r1, r2; + cpMat2x2 k; + + cpVect jAcc; + cpVect bias; +}; + +struct cpGrooveJoint { + cpConstraint constraint; + cpVect grv_n, grv_a, grv_b; + cpVect anchorB; + + cpVect grv_tn; + cpFloat clamp; + cpVect r1, r2; + cpMat2x2 k; + + cpVect jAcc; + cpVect bias; +}; + +struct cpDampedSpring { + cpConstraint constraint; + cpVect anchorA, anchorB; + cpFloat restLength; + cpFloat stiffness; + cpFloat damping; + cpDampedSpringForceFunc springForceFunc; + + cpFloat target_vrn; + cpFloat v_coef; + + cpVect r1, r2; + cpFloat nMass; + cpVect n; + + cpFloat jAcc; +}; + +struct cpDampedRotarySpring { + cpConstraint constraint; + cpFloat restAngle; + cpFloat stiffness; + cpFloat damping; + cpDampedRotarySpringTorqueFunc springTorqueFunc; + + cpFloat target_wrn; + cpFloat w_coef; + + cpFloat iSum; + cpFloat jAcc; +}; + +struct cpRotaryLimitJoint { + cpConstraint constraint; + cpFloat min, max; + + cpFloat iSum; + + cpFloat bias; + cpFloat jAcc; +}; + +struct cpRatchetJoint { + cpConstraint constraint; + cpFloat angle, phase, ratchet; + + cpFloat iSum; + + cpFloat bias; + cpFloat jAcc; +}; + +struct cpGearJoint { + cpConstraint constraint; + cpFloat phase, ratio; + cpFloat ratio_inv; + + cpFloat iSum; + + cpFloat bias; + cpFloat jAcc; +}; + +struct cpSimpleMotor { + cpConstraint constraint; + cpFloat rate; + + cpFloat iSum; + + cpFloat jAcc; +}; + +typedef struct cpContactBufferHeader cpContactBufferHeader; +typedef void (*cpSpaceArbiterApplyImpulseFunc)(cpArbiter *arb); + +struct cpSpace { + int iterations; + + cpVect gravity; + cpFloat damping; + + cpFloat idleSpeedThreshold; + cpFloat sleepTimeThreshold; + + cpFloat collisionSlop; + cpFloat collisionBias; + cpTimestamp collisionPersistence; + + cpDataPointer userData; + + cpTimestamp stamp; + cpFloat curr_dt; + + cpArray *dynamicBodies; + cpArray *staticBodies; + cpArray *rousedBodies; + cpArray *sleepingComponents; + + cpHashValue shapeIDCounter; + cpSpatialIndex *staticShapes; + cpSpatialIndex *dynamicShapes; + + cpArray *constraints; + + cpArray *arbiters; + cpContactBufferHeader *contactBuffersHead; + cpHashSet *cachedArbiters; + cpArray *pooledArbiters; + + cpArray *allocatedBuffers; + unsigned int locked; + + cpBool usesWildcards; + cpHashSet *collisionHandlers; + cpCollisionHandler defaultHandler; + + cpBool skipPostStep; + cpArray *postStepCallbacks; + + cpBody *staticBody; + cpBody _staticBody; +}; + +typedef struct cpPostStepCallback { + cpPostStepFunc func; + void *key; + void *data; +} cpPostStepCallback; + +#endif diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_types.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_types.h" new file mode 100644 index 0000000..0816727 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_types.h" @@ -0,0 +1,268 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#ifndef CHIPMUNK_TYPES_H +#define CHIPMUNK_TYPES_H + +#include +#include +#include + +#ifdef __APPLE__ + #include "TargetConditionals.h" +#endif + +// Use CGTypes by default on iOS and Mac. +// Also enables usage of doubles on 64 bit. +// Performance is usually very comparable when the CPU cache is well utilised. +#if (TARGET_OS_IPHONE || TARGET_OS_MAC) && (!defined CP_USE_CGTYPES) + #define CP_USE_CGTYPES 1 +#endif + +#if CP_USE_CGTYPES + #if TARGET_OS_IPHONE + #import + #import + #elif TARGET_OS_MAC + #include + #endif + + #if defined(__LP64__) && __LP64__ + #define CP_USE_DOUBLES 1 + #else + #define CP_USE_DOUBLES 0 + #endif +#endif + +#ifndef CP_USE_DOUBLES + // Use doubles by default for higher precision. + #define CP_USE_DOUBLES 1 +#endif + +/// @defgroup basicTypes Basic Types +/// Most of these types can be configured at compile time. +/// @{ + +#if CP_USE_DOUBLES +/// Chipmunk's floating point type. +/// Can be reconfigured at compile time. + typedef double cpFloat; + #define cpfsqrt sqrt + #define cpfsin sin + #define cpfcos cos + #define cpfacos acos + #define cpfatan2 atan2 + #define cpfmod fmod + #define cpfexp exp + #define cpfpow pow + #define cpffloor floor + #define cpfceil ceil + #define CPFLOAT_MIN DBL_MIN +#else + typedef float cpFloat; + #define cpfsqrt sqrtf + #define cpfsin sinf + #define cpfcos cosf + #define cpfacos acosf + #define cpfatan2 atan2f + #define cpfmod fmodf + #define cpfexp expf + #define cpfpow powf + #define cpffloor floorf + #define cpfceil ceilf + #define CPFLOAT_MIN FLT_MIN +#endif + +#ifndef INFINITY + #ifdef _MSC_VER + union MSVC_EVIL_FLOAT_HACK + { + unsigned __int8 Bytes[4]; + float Value; + }; + static union MSVC_EVIL_FLOAT_HACK INFINITY_HACK = {{0x00, 0x00, 0x80, 0x7F}}; + #define INFINITY (INFINITY_HACK.Value) + #endif + + #ifdef __GNUC__ + #define INFINITY (__builtin_inf()) + #endif + + #ifndef INFINITY + #define INFINITY (1e1000) + #endif +#endif + + +#define CP_PI ((cpFloat)3.14159265358979323846264338327950288) + + +/// Return the max of two cpFloats. +static inline cpFloat cpfmax(cpFloat a, cpFloat b) +{ + return (a > b) ? a : b; +} + +/// Return the min of two cpFloats. +static inline cpFloat cpfmin(cpFloat a, cpFloat b) +{ + return (a < b) ? a : b; +} + +/// Return the absolute value of a cpFloat. +static inline cpFloat cpfabs(cpFloat f) +{ + return (f < 0) ? -f : f; +} + +/// Clamp @c f to be between @c min and @c max. +static inline cpFloat cpfclamp(cpFloat f, cpFloat min, cpFloat max) +{ + return cpfmin(cpfmax(f, min), max); +} + +/// Clamp @c f to be between 0 and 1. +static inline cpFloat cpfclamp01(cpFloat f) +{ + return cpfmax(0.0f, cpfmin(f, 1.0f)); +} + + + +/// Linearly interpolate (or extrapolate) between @c f1 and @c f2 by @c t percent. +static inline cpFloat cpflerp(cpFloat f1, cpFloat f2, cpFloat t) +{ + return f1*(1.0f - t) + f2*t; +} + +/// Linearly interpolate from @c f1 to @c f2 by no more than @c d. +static inline cpFloat cpflerpconst(cpFloat f1, cpFloat f2, cpFloat d) +{ + return f1 + cpfclamp(f2 - f1, -d, d); +} + +/// Hash value type. +#ifdef CP_HASH_VALUE_TYPE + typedef CP_HASH_VALUE_TYPE cpHashValue; +#else + typedef uintptr_t cpHashValue; +#endif + +/// Type used internally to cache colliding object info for cpCollideShapes(). +/// Should be at least 32 bits. +typedef uint32_t cpCollisionID; + +// Oh C, how we love to define our own boolean types to get compiler compatibility +/// Chipmunk's boolean type. +#ifdef CP_BOOL_TYPE + typedef CP_BOOL_TYPE cpBool; +#else + typedef unsigned char cpBool; +#endif + +#ifndef cpTrue +/// true value. + #define cpTrue 1 +#endif + +#ifndef cpFalse +/// false value. + #define cpFalse 0 +#endif + +#ifdef CP_DATA_POINTER_TYPE + typedef CP_DATA_POINTER_TYPE cpDataPointer; +#else +/// Type used for user data pointers. + typedef void * cpDataPointer; +#endif + +#ifdef CP_COLLISION_TYPE_TYPE + typedef CP_COLLISION_TYPE_TYPE cpCollisionType; +#else +/// Type used for cpSpace.collision_type. + typedef uintptr_t cpCollisionType; +#endif + +#ifdef CP_GROUP_TYPE + typedef CP_GROUP_TYPE cpGroup; +#else +/// Type used for cpShape.group. + typedef uintptr_t cpGroup; +#endif + +#ifdef CP_BITMASK_TYPE + typedef CP_BITMASK_TYPE cpBitmask; +#else +/// Type used for cpShapeFilter category and mask. + typedef unsigned int cpBitmask; +#endif + +#ifdef CP_TIMESTAMP_TYPE + typedef CP_TIMESTAMP_TYPE cpTimestamp; +#else +/// Type used for various timestamps in Chipmunk. + typedef unsigned int cpTimestamp; +#endif + +#ifndef CP_NO_GROUP +/// Value for cpShape.group signifying that a shape is in no group. + #define CP_NO_GROUP ((cpGroup)0) +#endif + +#ifndef CP_ALL_CATEGORIES +/// Value for cpShape.layers signifying that a shape is in every layer. + #define CP_ALL_CATEGORIES (~(cpBitmask)0) +#endif + +#ifndef CP_WILDCARD_COLLISION_TYPE +/// cpCollisionType value internally reserved for hashing wildcard handlers. + #define CP_WILDCARD_COLLISION_TYPE (~(cpCollisionType)0) +#endif + +/// @} + +// CGPoints are structurally the same, and allow +// easy interoperability with other Cocoa libraries +#if CP_USE_CGTYPES + typedef CGPoint cpVect; +#else +/// Chipmunk's 2D vector type. +/// @addtogroup cpVect + typedef struct cpVect{cpFloat x,y;} cpVect; +#endif + +#if CP_USE_CGTYPES + typedef CGAffineTransform cpTransform; +#else + /// Column major affine transform. + typedef struct cpTransform { + cpFloat a, b, c, d, tx, ty; + } cpTransform; +#endif + +// NUKE +typedef struct cpMat2x2 { + // Row major [[a, b][c d]] + cpFloat a, b, c, d; +} cpMat2x2; + +#endif diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_unsafe.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_unsafe.h" new file mode 100644 index 0000000..990bd01 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_unsafe.h" @@ -0,0 +1,66 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/* This header defines a number of "unsafe" operations on Chipmunk objects. + * In this case "unsafe" is referring to operations which may reduce the + * physical accuracy or numerical stability of the simulation, but will not + * cause crashes. + * + * The prime example is mutating collision shapes. Chipmunk does not support + * this directly. Mutating shapes using this API will caused objects in contact + * to be pushed apart using Chipmunk's overlap solver, but not using real + * persistent velocities. Probably not what you meant, but perhaps close enough. + */ + +/// @defgroup unsafe Chipmunk Unsafe Shape Operations +/// These functions are used for mutating collision shapes. +/// Chipmunk does not have any way to get velocity information on changing shapes, +/// so the results will be unrealistic. You must explicity include the chipmunk_unsafe.h header to use them. +/// @{ + +#ifndef CHIPMUNK_UNSAFE_H +#define CHIPMUNK_UNSAFE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/// Set the radius of a circle shape. +CP_EXPORT void cpCircleShapeSetRadius(cpShape *shape, cpFloat radius); +/// Set the offset of a circle shape. +CP_EXPORT void cpCircleShapeSetOffset(cpShape *shape, cpVect offset); + +/// Set the endpoints of a segment shape. +CP_EXPORT void cpSegmentShapeSetEndpoints(cpShape *shape, cpVect a, cpVect b); +/// Set the radius of a segment shape. +CP_EXPORT void cpSegmentShapeSetRadius(cpShape *shape, cpFloat radius); + +/// Set the vertexes of a poly shape. +CP_EXPORT void cpPolyShapeSetVerts(cpShape *shape, int count, cpVect *verts, cpTransform transform); +CP_EXPORT void cpPolyShapeSetVertsRaw(cpShape *shape, int count, cpVect *verts); +/// Set the radius of a poly shape. +CP_EXPORT void cpPolyShapeSetRadius(cpShape *shape, cpFloat radius); + +#ifdef __cplusplus +} +#endif +#endif +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpArbiter.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpArbiter.h" new file mode 100644 index 0000000..1dc130a --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpArbiter.h" @@ -0,0 +1,145 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpArbiter cpArbiter +/// The cpArbiter struct tracks pairs of colliding shapes. +/// They are also used in conjuction with collision handler callbacks +/// allowing you to retrieve information on the collision or change it. +/// A unique arbiter value is used for each pair of colliding objects. It persists until the shapes separate. +/// @{ + +#define CP_MAX_CONTACTS_PER_ARBITER 2 + +/// Get the restitution (elasticity) that will be applied to the pair of colliding objects. +CP_EXPORT cpFloat cpArbiterGetRestitution(const cpArbiter *arb); +/// Override the restitution (elasticity) that will be applied to the pair of colliding objects. +CP_EXPORT void cpArbiterSetRestitution(cpArbiter *arb, cpFloat restitution); +/// Get the friction coefficient that will be applied to the pair of colliding objects. +CP_EXPORT cpFloat cpArbiterGetFriction(const cpArbiter *arb); +/// Override the friction coefficient that will be applied to the pair of colliding objects. +CP_EXPORT void cpArbiterSetFriction(cpArbiter *arb, cpFloat friction); + +// Get the relative surface velocity of the two shapes in contact. +CP_EXPORT cpVect cpArbiterGetSurfaceVelocity(cpArbiter *arb); + +// Override the relative surface velocity of the two shapes in contact. +// By default this is calculated to be the difference of the two surface velocities clamped to the tangent plane. +CP_EXPORT void cpArbiterSetSurfaceVelocity(cpArbiter *arb, cpVect vr); + +/// Get the user data pointer associated with this pair of colliding objects. +CP_EXPORT cpDataPointer cpArbiterGetUserData(const cpArbiter *arb); +/// Set a user data point associated with this pair of colliding objects. +/// If you need to perform any cleanup for this pointer, you must do it yourself, in the separate callback for instance. +CP_EXPORT void cpArbiterSetUserData(cpArbiter *arb, cpDataPointer userData); + +/// Calculate the total impulse including the friction that was applied by this arbiter. +/// This function should only be called from a post-solve, post-step or cpBodyEachArbiter callback. +CP_EXPORT cpVect cpArbiterTotalImpulse(const cpArbiter *arb); +/// Calculate the amount of energy lost in a collision including static, but not dynamic friction. +/// This function should only be called from a post-solve, post-step or cpBodyEachArbiter callback. +CP_EXPORT cpFloat cpArbiterTotalKE(const cpArbiter *arb); + +/// Mark a collision pair to be ignored until the two objects separate. +/// Pre-solve and post-solve callbacks will not be called, but the separate callback will be called. +CP_EXPORT cpBool cpArbiterIgnore(cpArbiter *arb); + +/// Return the colliding shapes involved for this arbiter. +/// The order of their cpSpace.collision_type values will match +/// the order set when the collision handler was registered. +CP_EXPORT void cpArbiterGetShapes(const cpArbiter *arb, cpShape **a, cpShape **b); + +/// A macro shortcut for defining and retrieving the shapes from an arbiter. +#define CP_ARBITER_GET_SHAPES(__arb__, __a__, __b__) cpShape *__a__, *__b__; cpArbiterGetShapes(__arb__, &__a__, &__b__); + +/// Return the colliding bodies involved for this arbiter. +/// The order of the cpSpace.collision_type the bodies are associated with values will match +/// the order set when the collision handler was registered. +CP_EXPORT void cpArbiterGetBodies(const cpArbiter *arb, cpBody **a, cpBody **b); + +/// A macro shortcut for defining and retrieving the bodies from an arbiter. +#define CP_ARBITER_GET_BODIES(__arb__, __a__, __b__) cpBody *__a__, *__b__; cpArbiterGetBodies(__arb__, &__a__, &__b__); + +/// A struct that wraps up the important collision data for an arbiter. +struct cpContactPointSet { + /// The number of contact points in the set. + int count; + + /// The normal of the collision. + cpVect normal; + + /// The array of contact points. + struct { + /// The position of the contact on the surface of each shape. + cpVect pointA, pointB; + /// Penetration distance of the two shapes. Overlapping means it will be negative. + /// This value is calculated as cpvdot(cpvsub(point2, point1), normal) and is ignored by cpArbiterSetContactPointSet(). + cpFloat distance; + } points[CP_MAX_CONTACTS_PER_ARBITER]; +}; + +/// Return a contact set from an arbiter. +CP_EXPORT cpContactPointSet cpArbiterGetContactPointSet(const cpArbiter *arb); + +/// Replace the contact point set for an arbiter. +/// This can be a very powerful feature, but use it with caution! +CP_EXPORT void cpArbiterSetContactPointSet(cpArbiter *arb, cpContactPointSet *set); + +/// Returns true if this is the first step a pair of objects started colliding. +CP_EXPORT cpBool cpArbiterIsFirstContact(const cpArbiter *arb); +/// Returns true if the separate callback is due to a shape being removed from the space. +CP_EXPORT cpBool cpArbiterIsRemoval(const cpArbiter *arb); + +/// Get the number of contact points for this arbiter. +CP_EXPORT int cpArbiterGetCount(const cpArbiter *arb); +/// Get the normal of the collision. +CP_EXPORT cpVect cpArbiterGetNormal(const cpArbiter *arb); +/// Get the position of the @c ith contact point on the surface of the first shape. +CP_EXPORT cpVect cpArbiterGetPointA(const cpArbiter *arb, int i); +/// Get the position of the @c ith contact point on the surface of the second shape. +CP_EXPORT cpVect cpArbiterGetPointB(const cpArbiter *arb, int i); +/// Get the depth of the @c ith contact point. +CP_EXPORT cpFloat cpArbiterGetDepth(const cpArbiter *arb, int i); + +/// If you want a custom callback to invoke the wildcard callback for the first collision type, you must call this function explicitly. +/// You must decide how to handle the wildcard's return value since it may disagree with the other wildcard handler's return value or your own. +CP_EXPORT cpBool cpArbiterCallWildcardBeginA(cpArbiter *arb, cpSpace *space); +/// If you want a custom callback to invoke the wildcard callback for the second collision type, you must call this function explicitly. +/// You must decide how to handle the wildcard's return value since it may disagree with the other wildcard handler's return value or your own. +CP_EXPORT cpBool cpArbiterCallWildcardBeginB(cpArbiter *arb, cpSpace *space); + +/// If you want a custom callback to invoke the wildcard callback for the first collision type, you must call this function explicitly. +/// You must decide how to handle the wildcard's return value since it may disagree with the other wildcard handler's return value or your own. +CP_EXPORT cpBool cpArbiterCallWildcardPreSolveA(cpArbiter *arb, cpSpace *space); +/// If you want a custom callback to invoke the wildcard callback for the second collision type, you must call this function explicitly. +/// You must decide how to handle the wildcard's return value since it may disagree with the other wildcard handler's return value or your own. +CP_EXPORT cpBool cpArbiterCallWildcardPreSolveB(cpArbiter *arb, cpSpace *space); + +/// If you want a custom callback to invoke the wildcard callback for the first collision type, you must call this function explicitly. +CP_EXPORT void cpArbiterCallWildcardPostSolveA(cpArbiter *arb, cpSpace *space); +/// If you want a custom callback to invoke the wildcard callback for the second collision type, you must call this function explicitly. +CP_EXPORT void cpArbiterCallWildcardPostSolveB(cpArbiter *arb, cpSpace *space); + +/// If you want a custom callback to invoke the wildcard callback for the first collision type, you must call this function explicitly. +CP_EXPORT void cpArbiterCallWildcardSeparateA(cpArbiter *arb, cpSpace *space); +/// If you want a custom callback to invoke the wildcard callback for the second collision type, you must call this function explicitly. +CP_EXPORT void cpArbiterCallWildcardSeparateB(cpArbiter *arb, cpSpace *space); + +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBB.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBB.h" new file mode 100644 index 0000000..55d3b34 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBB.h" @@ -0,0 +1,183 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#ifndef CHIPMUNK_BB_H +#define CHIPMUNK_BB_H + +#include "chipmunk_types.h" +#include "cpVect.h" + +/// @defgroup cpBBB cpBB +/// Chipmunk's axis-aligned 2D bounding box type along with a few handy routines. +/// @{ + +/// Chipmunk's axis-aligned 2D bounding box type. (left, bottom, right, top) +typedef struct cpBB{ + cpFloat l, b, r ,t; +} cpBB; + +/// Convenience constructor for cpBB structs. +static inline cpBB cpBBNew(const cpFloat l, const cpFloat b, const cpFloat r, const cpFloat t) +{ + cpBB bb = {l, b, r, t}; + return bb; +} + +/// Constructs a cpBB centered on a point with the given extents (half sizes). +static inline cpBB +cpBBNewForExtents(const cpVect c, const cpFloat hw, const cpFloat hh) +{ + return cpBBNew(c.x - hw, c.y - hh, c.x + hw, c.y + hh); +} + +/// Constructs a cpBB for a circle with the given position and radius. +static inline cpBB cpBBNewForCircle(const cpVect p, const cpFloat r) +{ + return cpBBNewForExtents(p, r, r); +} + +/// Returns true if @c a and @c b intersect. +static inline cpBool cpBBIntersects(const cpBB a, const cpBB b) +{ + return (a.l <= b.r && b.l <= a.r && a.b <= b.t && b.b <= a.t); +} + +/// Returns true if @c other lies completely within @c bb. +static inline cpBool cpBBContainsBB(const cpBB bb, const cpBB other) +{ + return (bb.l <= other.l && bb.r >= other.r && bb.b <= other.b && bb.t >= other.t); +} + +/// Returns true if @c bb contains @c v. +static inline cpBool cpBBContainsVect(const cpBB bb, const cpVect v) +{ + return (bb.l <= v.x && bb.r >= v.x && bb.b <= v.y && bb.t >= v.y); +} + +/// Returns a bounding box that holds both bounding boxes. +static inline cpBB cpBBMerge(const cpBB a, const cpBB b){ + return cpBBNew( + cpfmin(a.l, b.l), + cpfmin(a.b, b.b), + cpfmax(a.r, b.r), + cpfmax(a.t, b.t) + ); +} + +/// Returns a bounding box that holds both @c bb and @c v. +static inline cpBB cpBBExpand(const cpBB bb, const cpVect v){ + return cpBBNew( + cpfmin(bb.l, v.x), + cpfmin(bb.b, v.y), + cpfmax(bb.r, v.x), + cpfmax(bb.t, v.y) + ); +} + +/// Returns the center of a bounding box. +static inline cpVect +cpBBCenter(cpBB bb) +{ + return cpvlerp(cpv(bb.l, bb.b), cpv(bb.r, bb.t), 0.5f); +} + +/// Returns the area of the bounding box. +static inline cpFloat cpBBArea(cpBB bb) +{ + return (bb.r - bb.l)*(bb.t - bb.b); +} + +/// Merges @c a and @c b and returns the area of the merged bounding box. +static inline cpFloat cpBBMergedArea(cpBB a, cpBB b) +{ + return (cpfmax(a.r, b.r) - cpfmin(a.l, b.l))*(cpfmax(a.t, b.t) - cpfmin(a.b, b.b)); +} + +/// Returns the fraction along the segment query the cpBB is hit. Returns INFINITY if it doesn't hit. +static inline cpFloat cpBBSegmentQuery(cpBB bb, cpVect a, cpVect b) +{ + cpVect delta = cpvsub(b, a); + cpFloat tmin = -INFINITY, tmax = INFINITY; + + if(delta.x != 0.0f){ + cpFloat t1 = (bb.l - a.x)/delta.x; + cpFloat t2 = (bb.r - a.x)/delta.x; + tmin = cpfmax(tmin, cpfmin(t1, t2)); + tmax = cpfmin(tmax, cpfmax(t1, t2)); + } + + if(delta.y != 0.0f){ + cpFloat t1 = (bb.b - a.y)/delta.y; + cpFloat t2 = (bb.t - a.y)/delta.y; + tmin = cpfmax(tmin, cpfmin(t1, t2)); + tmax = cpfmin(tmax, cpfmax(t1, t2)); + } + + if(tmin <= tmax && 0.0f <= tmax && tmin <= 1.0f){ + return cpfmax(tmin, 0.0f); + } + + return INFINITY; +} + +/// Return true if the bounding box intersects the line segment with ends @c a and @c b. +static inline cpBool cpBBIntersectsSegment(cpBB bb, cpVect a, cpVect b) +{ + return (cpBBSegmentQuery(bb, a, b) != INFINITY); +} + +/// Clamp a vector to a bounding box. +static inline cpVect +cpBBClampVect(const cpBB bb, const cpVect v) +{ + return cpv(cpfclamp(v.x, bb.l, bb.r), cpfclamp(v.y, bb.b, bb.t)); +} + +/// Wrap a vector to a bounding box. +static inline cpVect +cpBBWrapVect(const cpBB bb, const cpVect v) +{ + cpFloat dx = cpfabs(bb.r - bb.l); + cpFloat modx = cpfmod(v.x - bb.l, dx); + cpFloat x = (modx > 0.0f) ? modx : modx + dx; + + cpFloat dy = cpfabs(bb.t - bb.b); + cpFloat mody = cpfmod(v.y - bb.b, dy); + cpFloat y = (mody > 0.0f) ? mody : mody + dy; + + return cpv(x + bb.l, y + bb.b); +} + +/// Returns a bounding box offseted by @c v. +static inline cpBB +cpBBOffset(const cpBB bb, const cpVect v) +{ + return cpBBNew( + bb.l + v.x, + bb.b + v.y, + bb.r + v.x, + bb.t + v.y + ); +} + +///@} + +#endif diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBody.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBody.h" new file mode 100644 index 0000000..7e6943d --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBody.h" @@ -0,0 +1,189 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpBody cpBody +/// Chipmunk's rigid body type. Rigid bodies hold the physical properties of an object like +/// it's mass, and position and velocity of it's center of gravity. They don't have an shape on their own. +/// They are given a shape by creating collision shapes (cpShape) that point to the body. +/// @{ + +typedef enum cpBodyType { + /// A dynamic body is one that is affected by gravity, forces, and collisions. + /// This is the default body type. + CP_BODY_TYPE_DYNAMIC, + /// A kinematic body is an infinite mass, user controlled body that is not affected by gravity, forces or collisions. + /// Instead the body only moves based on it's velocity. + /// Dynamic bodies collide normally with kinematic bodies, though the kinematic body will be unaffected. + /// Collisions between two kinematic bodies, or a kinematic body and a static body produce collision callbacks, but no collision response. + CP_BODY_TYPE_KINEMATIC, + /// A static body is a body that never (or rarely) moves. If you move a static body, you must call one of the cpSpaceReindex*() functions. + /// Chipmunk uses this information to optimize the collision detection. + /// Static bodies do not produce collision callbacks when colliding with other static bodies. + CP_BODY_TYPE_STATIC, +} cpBodyType; + +/// Rigid body velocity update function type. +typedef void (*cpBodyVelocityFunc)(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt); +/// Rigid body position update function type. +typedef void (*cpBodyPositionFunc)(cpBody *body, cpFloat dt); + +/// Allocate a cpBody. +CP_EXPORT cpBody* cpBodyAlloc(void); +/// Initialize a cpBody. +CP_EXPORT cpBody* cpBodyInit(cpBody *body, cpFloat mass, cpFloat moment); +/// Allocate and initialize a cpBody. +CP_EXPORT cpBody* cpBodyNew(cpFloat mass, cpFloat moment); + +/// Allocate and initialize a cpBody, and set it as a kinematic body. +CP_EXPORT cpBody* cpBodyNewKinematic(void); +/// Allocate and initialize a cpBody, and set it as a static body. +CP_EXPORT cpBody* cpBodyNewStatic(void); + +/// Destroy a cpBody. +CP_EXPORT void cpBodyDestroy(cpBody *body); +/// Destroy and free a cpBody. +CP_EXPORT void cpBodyFree(cpBody *body); + +// Defined in cpSpace.c +/// Wake up a sleeping or idle body. +CP_EXPORT void cpBodyActivate(cpBody *body); +/// Wake up any sleeping or idle bodies touching a static body. +CP_EXPORT void cpBodyActivateStatic(cpBody *body, cpShape *filter); + +/// Force a body to fall asleep immediately. +CP_EXPORT void cpBodySleep(cpBody *body); +/// Force a body to fall asleep immediately along with other bodies in a group. +CP_EXPORT void cpBodySleepWithGroup(cpBody *body, cpBody *group); + +/// Returns true if the body is sleeping. +CP_EXPORT cpBool cpBodyIsSleeping(const cpBody *body); + +/// Get the type of the body. +CP_EXPORT cpBodyType cpBodyGetType(cpBody *body); +/// Set the type of the body. +CP_EXPORT void cpBodySetType(cpBody *body, cpBodyType type); + +/// Get the space this body is added to. +CP_EXPORT cpSpace* cpBodyGetSpace(const cpBody *body); + +/// Get the mass of the body. +CP_EXPORT cpFloat cpBodyGetMass(const cpBody *body); +/// Set the mass of the body. +CP_EXPORT void cpBodySetMass(cpBody *body, cpFloat m); + +/// Get the moment of inertia of the body. +CP_EXPORT cpFloat cpBodyGetMoment(const cpBody *body); +/// Set the moment of inertia of the body. +CP_EXPORT void cpBodySetMoment(cpBody *body, cpFloat i); + +/// Set the position of a body. +CP_EXPORT cpVect cpBodyGetPosition(const cpBody *body); +/// Set the position of the body. +CP_EXPORT void cpBodySetPosition(cpBody *body, cpVect pos); + +/// Get the offset of the center of gravity in body local coordinates. +CP_EXPORT cpVect cpBodyGetCenterOfGravity(const cpBody *body); +/// Set the offset of the center of gravity in body local coordinates. +CP_EXPORT void cpBodySetCenterOfGravity(cpBody *body, cpVect cog); + +/// Get the velocity of the body. +CP_EXPORT cpVect cpBodyGetVelocity(const cpBody *body); +/// Set the velocity of the body. +CP_EXPORT void cpBodySetVelocity(cpBody *body, cpVect velocity); + +/// Get the force applied to the body for the next time step. +CP_EXPORT cpVect cpBodyGetForce(const cpBody *body); +/// Set the force applied to the body for the next time step. +CP_EXPORT void cpBodySetForce(cpBody *body, cpVect force); + +/// Get the angle of the body. +CP_EXPORT cpFloat cpBodyGetAngle(const cpBody *body); +/// Set the angle of a body. +CP_EXPORT void cpBodySetAngle(cpBody *body, cpFloat a); + +/// Get the angular velocity of the body. +CP_EXPORT cpFloat cpBodyGetAngularVelocity(const cpBody *body); +/// Set the angular velocity of the body. +CP_EXPORT void cpBodySetAngularVelocity(cpBody *body, cpFloat angularVelocity); + +/// Get the torque applied to the body for the next time step. +CP_EXPORT cpFloat cpBodyGetTorque(const cpBody *body); +/// Set the torque applied to the body for the next time step. +CP_EXPORT void cpBodySetTorque(cpBody *body, cpFloat torque); + +/// Get the rotation vector of the body. (The x basis vector of it's transform.) +CP_EXPORT cpVect cpBodyGetRotation(const cpBody *body); + +/// Get the user data pointer assigned to the body. +CP_EXPORT cpDataPointer cpBodyGetUserData(const cpBody *body); +/// Set the user data pointer assigned to the body. +CP_EXPORT void cpBodySetUserData(cpBody *body, cpDataPointer userData); + +/// Set the callback used to update a body's velocity. +CP_EXPORT void cpBodySetVelocityUpdateFunc(cpBody *body, cpBodyVelocityFunc velocityFunc); +/// Set the callback used to update a body's position. +/// NOTE: It's not generally recommended to override this unless you call the default position update function. +CP_EXPORT void cpBodySetPositionUpdateFunc(cpBody *body, cpBodyPositionFunc positionFunc); + +/// Default velocity integration function.. +CP_EXPORT void cpBodyUpdateVelocity(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt); +/// Default position integration function. +CP_EXPORT void cpBodyUpdatePosition(cpBody *body, cpFloat dt); + +/// Convert body relative/local coordinates to absolute/world coordinates. +CP_EXPORT cpVect cpBodyLocalToWorld(const cpBody *body, const cpVect point); +/// Convert body absolute/world coordinates to relative/local coordinates. +CP_EXPORT cpVect cpBodyWorldToLocal(const cpBody *body, const cpVect point); + +/// Apply a force to a body. Both the force and point are expressed in world coordinates. +CP_EXPORT void cpBodyApplyForceAtWorldPoint(cpBody *body, cpVect force, cpVect point); +/// Apply a force to a body. Both the force and point are expressed in body local coordinates. +CP_EXPORT void cpBodyApplyForceAtLocalPoint(cpBody *body, cpVect force, cpVect point); + +/// Apply an impulse to a body. Both the impulse and point are expressed in world coordinates. +CP_EXPORT void cpBodyApplyImpulseAtWorldPoint(cpBody *body, cpVect impulse, cpVect point); +/// Apply an impulse to a body. Both the impulse and point are expressed in body local coordinates. +CP_EXPORT void cpBodyApplyImpulseAtLocalPoint(cpBody *body, cpVect impulse, cpVect point); + +/// Get the velocity on a body (in world units) at a point on the body in world coordinates. +CP_EXPORT cpVect cpBodyGetVelocityAtWorldPoint(const cpBody *body, cpVect point); +/// Get the velocity on a body (in world units) at a point on the body in local coordinates. +CP_EXPORT cpVect cpBodyGetVelocityAtLocalPoint(const cpBody *body, cpVect point); + +/// Get the amount of kinetic energy contained by the body. +CP_EXPORT cpFloat cpBodyKineticEnergy(const cpBody *body); + +/// Body/shape iterator callback function type. +typedef void (*cpBodyShapeIteratorFunc)(cpBody *body, cpShape *shape, void *data); +/// Call @c func once for each shape attached to @c body and added to the space. +CP_EXPORT void cpBodyEachShape(cpBody *body, cpBodyShapeIteratorFunc func, void *data); + +/// Body/constraint iterator callback function type. +typedef void (*cpBodyConstraintIteratorFunc)(cpBody *body, cpConstraint *constraint, void *data); +/// Call @c func once for each constraint attached to @c body and added to the space. +CP_EXPORT void cpBodyEachConstraint(cpBody *body, cpBodyConstraintIteratorFunc func, void *data); + +/// Body/arbiter iterator callback function type. +typedef void (*cpBodyArbiterIteratorFunc)(cpBody *body, cpArbiter *arbiter, void *data); +/// Call @c func once for each arbiter that is currently active on the body. +CP_EXPORT void cpBodyEachArbiter(cpBody *body, cpBodyArbiterIteratorFunc func, void *data); + +///@} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpConstraint.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpConstraint.h" new file mode 100644 index 0000000..b1a439f --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpConstraint.h" @@ -0,0 +1,95 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpConstraint cpConstraint +/// @{ + +/// Callback function type that gets called before solving a joint. +typedef void (*cpConstraintPreSolveFunc)(cpConstraint *constraint, cpSpace *space); +/// Callback function type that gets called after solving a joint. +typedef void (*cpConstraintPostSolveFunc)(cpConstraint *constraint, cpSpace *space); + +/// Destroy a constraint. +CP_EXPORT void cpConstraintDestroy(cpConstraint *constraint); +/// Destroy and free a constraint. +CP_EXPORT void cpConstraintFree(cpConstraint *constraint); + +/// Get the cpSpace this constraint is added to. +CP_EXPORT cpSpace* cpConstraintGetSpace(const cpConstraint *constraint); + +/// Get the first body the constraint is attached to. +CP_EXPORT cpBody* cpConstraintGetBodyA(const cpConstraint *constraint); + +/// Get the second body the constraint is attached to. +CP_EXPORT cpBody* cpConstraintGetBodyB(const cpConstraint *constraint); + +/// Get the maximum force that this constraint is allowed to use. +CP_EXPORT cpFloat cpConstraintGetMaxForce(const cpConstraint *constraint); +/// Set the maximum force that this constraint is allowed to use. (defaults to INFINITY) +CP_EXPORT void cpConstraintSetMaxForce(cpConstraint *constraint, cpFloat maxForce); + +/// Get rate at which joint error is corrected. +CP_EXPORT cpFloat cpConstraintGetErrorBias(const cpConstraint *constraint); +/// Set rate at which joint error is corrected. +/// Defaults to pow(1.0 - 0.1, 60.0) meaning that it will +/// correct 10% of the error every 1/60th of a second. +CP_EXPORT void cpConstraintSetErrorBias(cpConstraint *constraint, cpFloat errorBias); + +/// Get the maximum rate at which joint error is corrected. +CP_EXPORT cpFloat cpConstraintGetMaxBias(const cpConstraint *constraint); +/// Set the maximum rate at which joint error is corrected. (defaults to INFINITY) +CP_EXPORT void cpConstraintSetMaxBias(cpConstraint *constraint, cpFloat maxBias); + +/// Get if the two bodies connected by the constraint are allowed to collide or not. +CP_EXPORT cpBool cpConstraintGetCollideBodies(const cpConstraint *constraint); +/// Set if the two bodies connected by the constraint are allowed to collide or not. (defaults to cpFalse) +CP_EXPORT void cpConstraintSetCollideBodies(cpConstraint *constraint, cpBool collideBodies); + +/// Get the pre-solve function that is called before the solver runs. +CP_EXPORT cpConstraintPreSolveFunc cpConstraintGetPreSolveFunc(const cpConstraint *constraint); +/// Set the pre-solve function that is called before the solver runs. +CP_EXPORT void cpConstraintSetPreSolveFunc(cpConstraint *constraint, cpConstraintPreSolveFunc preSolveFunc); + +/// Get the post-solve function that is called before the solver runs. +CP_EXPORT cpConstraintPostSolveFunc cpConstraintGetPostSolveFunc(const cpConstraint *constraint); +/// Set the post-solve function that is called before the solver runs. +CP_EXPORT void cpConstraintSetPostSolveFunc(cpConstraint *constraint, cpConstraintPostSolveFunc postSolveFunc); + +/// Get the user definable data pointer for this constraint +CP_EXPORT cpDataPointer cpConstraintGetUserData(const cpConstraint *constraint); +/// Set the user definable data pointer for this constraint +CP_EXPORT void cpConstraintSetUserData(cpConstraint *constraint, cpDataPointer userData); + +/// Get the last impulse applied by this constraint. +CP_EXPORT cpFloat cpConstraintGetImpulse(cpConstraint *constraint); + +#include "cpPinJoint.h" +#include "cpSlideJoint.h" +#include "cpPivotJoint.h" +#include "cpGrooveJoint.h" +#include "cpDampedSpring.h" +#include "cpDampedRotarySpring.h" +#include "cpRotaryLimitJoint.h" +#include "cpRatchetJoint.h" +#include "cpGearJoint.h" +#include "cpSimpleMotor.h" + +///@} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedRotarySpring.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedRotarySpring.h" new file mode 100644 index 0000000..6f60e86 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedRotarySpring.h" @@ -0,0 +1,58 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpDampedRotarySpring cpDampedRotarySpring +/// @{ + +/// Check if a constraint is a damped rotary springs. +CP_EXPORT cpBool cpConstraintIsDampedRotarySpring(const cpConstraint *constraint); + +/// Function type used for damped rotary spring force callbacks. +typedef cpFloat (*cpDampedRotarySpringTorqueFunc)(struct cpConstraint *spring, cpFloat relativeAngle); + +/// Allocate a damped rotary spring. +CP_EXPORT cpDampedRotarySpring* cpDampedRotarySpringAlloc(void); +/// Initialize a damped rotary spring. +CP_EXPORT cpDampedRotarySpring* cpDampedRotarySpringInit(cpDampedRotarySpring *joint, cpBody *a, cpBody *b, cpFloat restAngle, cpFloat stiffness, cpFloat damping); +/// Allocate and initialize a damped rotary spring. +CP_EXPORT cpConstraint* cpDampedRotarySpringNew(cpBody *a, cpBody *b, cpFloat restAngle, cpFloat stiffness, cpFloat damping); + +/// Get the rest length of the spring. +CP_EXPORT cpFloat cpDampedRotarySpringGetRestAngle(const cpConstraint *constraint); +/// Set the rest length of the spring. +CP_EXPORT void cpDampedRotarySpringSetRestAngle(cpConstraint *constraint, cpFloat restAngle); + +/// Get the stiffness of the spring in force/distance. +CP_EXPORT cpFloat cpDampedRotarySpringGetStiffness(const cpConstraint *constraint); +/// Set the stiffness of the spring in force/distance. +CP_EXPORT void cpDampedRotarySpringSetStiffness(cpConstraint *constraint, cpFloat stiffness); + +/// Get the damping of the spring. +CP_EXPORT cpFloat cpDampedRotarySpringGetDamping(const cpConstraint *constraint); +/// Set the damping of the spring. +CP_EXPORT void cpDampedRotarySpringSetDamping(cpConstraint *constraint, cpFloat damping); + +/// Get the damping of the spring. +CP_EXPORT cpDampedRotarySpringTorqueFunc cpDampedRotarySpringGetSpringTorqueFunc(const cpConstraint *constraint); +/// Set the damping of the spring. +CP_EXPORT void cpDampedRotarySpringSetSpringTorqueFunc(cpConstraint *constraint, cpDampedRotarySpringTorqueFunc springTorqueFunc); + +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedSpring.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedSpring.h" new file mode 100644 index 0000000..b332fc7 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedSpring.h" @@ -0,0 +1,68 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpDampedSpring cpDampedSpring +/// @{ + +/// Check if a constraint is a slide joint. +CP_EXPORT cpBool cpConstraintIsDampedSpring(const cpConstraint *constraint); + +/// Function type used for damped spring force callbacks. +typedef cpFloat (*cpDampedSpringForceFunc)(cpConstraint *spring, cpFloat dist); + +/// Allocate a damped spring. +CP_EXPORT cpDampedSpring* cpDampedSpringAlloc(void); +/// Initialize a damped spring. +CP_EXPORT cpDampedSpring* cpDampedSpringInit(cpDampedSpring *joint, cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB, cpFloat restLength, cpFloat stiffness, cpFloat damping); +/// Allocate and initialize a damped spring. +CP_EXPORT cpConstraint* cpDampedSpringNew(cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB, cpFloat restLength, cpFloat stiffness, cpFloat damping); + +/// Get the location of the first anchor relative to the first body. +CP_EXPORT cpVect cpDampedSpringGetAnchorA(const cpConstraint *constraint); +/// Set the location of the first anchor relative to the first body. +CP_EXPORT void cpDampedSpringSetAnchorA(cpConstraint *constraint, cpVect anchorA); + +/// Get the location of the second anchor relative to the second body. +CP_EXPORT cpVect cpDampedSpringGetAnchorB(const cpConstraint *constraint); +/// Set the location of the second anchor relative to the second body. +CP_EXPORT void cpDampedSpringSetAnchorB(cpConstraint *constraint, cpVect anchorB); + +/// Get the rest length of the spring. +CP_EXPORT cpFloat cpDampedSpringGetRestLength(const cpConstraint *constraint); +/// Set the rest length of the spring. +CP_EXPORT void cpDampedSpringSetRestLength(cpConstraint *constraint, cpFloat restLength); + +/// Get the stiffness of the spring in force/distance. +CP_EXPORT cpFloat cpDampedSpringGetStiffness(const cpConstraint *constraint); +/// Set the stiffness of the spring in force/distance. +CP_EXPORT void cpDampedSpringSetStiffness(cpConstraint *constraint, cpFloat stiffness); + +/// Get the damping of the spring. +CP_EXPORT cpFloat cpDampedSpringGetDamping(const cpConstraint *constraint); +/// Set the damping of the spring. +CP_EXPORT void cpDampedSpringSetDamping(cpConstraint *constraint, cpFloat damping); + +/// Get the damping of the spring. +CP_EXPORT cpDampedSpringForceFunc cpDampedSpringGetSpringForceFunc(const cpConstraint *constraint); +/// Set the damping of the spring. +CP_EXPORT void cpDampedSpringSetSpringForceFunc(cpConstraint *constraint, cpDampedSpringForceFunc springForceFunc); + +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGearJoint.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGearJoint.h" new file mode 100644 index 0000000..8cd80e0 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGearJoint.h" @@ -0,0 +1,45 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpGearJoint cpGearJoint +/// @{ + +/// Check if a constraint is a damped rotary springs. +CP_EXPORT cpBool cpConstraintIsGearJoint(const cpConstraint *constraint); + +/// Allocate a gear joint. +CP_EXPORT cpGearJoint* cpGearJointAlloc(void); +/// Initialize a gear joint. +CP_EXPORT cpGearJoint* cpGearJointInit(cpGearJoint *joint, cpBody *a, cpBody *b, cpFloat phase, cpFloat ratio); +/// Allocate and initialize a gear joint. +CP_EXPORT cpConstraint* cpGearJointNew(cpBody *a, cpBody *b, cpFloat phase, cpFloat ratio); + +/// Get the phase offset of the gears. +CP_EXPORT cpFloat cpGearJointGetPhase(const cpConstraint *constraint); +/// Set the phase offset of the gears. +CP_EXPORT void cpGearJointSetPhase(cpConstraint *constraint, cpFloat phase); + +/// Get the angular distance of each ratchet. +CP_EXPORT cpFloat cpGearJointGetRatio(const cpConstraint *constraint); +/// Set the ratio of a gear joint. +CP_EXPORT void cpGearJointSetRatio(cpConstraint *constraint, cpFloat ratio); + +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGrooveJoint.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGrooveJoint.h" new file mode 100644 index 0000000..8bdafc1 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGrooveJoint.h" @@ -0,0 +1,50 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpGrooveJoint cpGrooveJoint +/// @{ + +/// Check if a constraint is a slide joint. +CP_EXPORT cpBool cpConstraintIsGrooveJoint(const cpConstraint *constraint); + +/// Allocate a groove joint. +CP_EXPORT cpGrooveJoint* cpGrooveJointAlloc(void); +/// Initialize a groove joint. +CP_EXPORT cpGrooveJoint* cpGrooveJointInit(cpGrooveJoint *joint, cpBody *a, cpBody *b, cpVect groove_a, cpVect groove_b, cpVect anchorB); +/// Allocate and initialize a groove joint. +CP_EXPORT cpConstraint* cpGrooveJointNew(cpBody *a, cpBody *b, cpVect groove_a, cpVect groove_b, cpVect anchorB); + +/// Get the first endpoint of the groove relative to the first body. +CP_EXPORT cpVect cpGrooveJointGetGrooveA(const cpConstraint *constraint); +/// Set the first endpoint of the groove relative to the first body. +CP_EXPORT void cpGrooveJointSetGrooveA(cpConstraint *constraint, cpVect grooveA); + +/// Get the first endpoint of the groove relative to the first body. +CP_EXPORT cpVect cpGrooveJointGetGrooveB(const cpConstraint *constraint); +/// Set the first endpoint of the groove relative to the first body. +CP_EXPORT void cpGrooveJointSetGrooveB(cpConstraint *constraint, cpVect grooveB); + +/// Get the location of the second anchor relative to the second body. +CP_EXPORT cpVect cpGrooveJointGetAnchorB(const cpConstraint *constraint); +/// Set the location of the second anchor relative to the second body. +CP_EXPORT void cpGrooveJointSetAnchorB(cpConstraint *constraint, cpVect anchorB); + +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpHastySpace.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpHastySpace.h" new file mode 100644 index 0000000..6de2283 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpHastySpace.h" @@ -0,0 +1,27 @@ +// Copyright 2013 Howling Moon Software. All rights reserved. +// See http://chipmunk2d.net/legal.php for more information. + +/// cpHastySpace is exclusive to Chipmunk Pro +/// Currently it enables ARM NEON optimizations in the solver, but in the future will include other optimizations such as +/// a multi-threaded solver and multi-threaded collision broadphases. + +struct cpHastySpace; +typedef struct cpHastySpace cpHastySpace; + +/// Create a new hasty space. +/// On ARM platforms that support NEON, this will enable the vectorized solver. +/// cpHastySpace also supports multiple threads, but runs single threaded by default for determinism. +CP_EXPORT cpSpace *cpHastySpaceNew(void); +CP_EXPORT void cpHastySpaceFree(cpSpace *space); + +/// Set the number of threads to use for the solver. +/// Currently Chipmunk is limited to 2 threads as using more generally provides very minimal performance gains. +/// Passing 0 as the thread count on iOS or OS X will cause Chipmunk to automatically detect the number of threads it should use. +/// On other platforms passing 0 for the thread count will set 1 thread. +CP_EXPORT void cpHastySpaceSetThreads(cpSpace *space, unsigned long threads); + +/// Returns the number of threads the solver is using to run. +CP_EXPORT unsigned long cpHastySpaceGetThreads(cpSpace *space); + +/// When stepping a hasty space, you must use this function. +CP_EXPORT void cpHastySpaceStep(cpSpace *space, cpFloat dt); diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpMarch.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpMarch.h" new file mode 100644 index 0000000..cc1f5c0 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpMarch.h" @@ -0,0 +1,28 @@ +// Copyright 2013 Howling Moon Software. All rights reserved. +// See http://chipmunk2d.net/legal.php for more information. + +/// Function type used as a callback from the marching squares algorithm to sample an image function. +/// It passes you the point to sample and your context pointer, and you return the density. +typedef cpFloat (*cpMarchSampleFunc)(cpVect point, void *data); + +/// Function type used as a callback from the marching squares algorithm to output a line segment. +/// It passes you the two endpoints and your context pointer. +typedef void (*cpMarchSegmentFunc)(cpVect v0, cpVect v1, void *data); + +/// Trace an anti-aliased contour of an image along a particular threshold. +/// The given number of samples will be taken and spread across the bounding box area using the sampling function and context. +/// The segment function will be called for each segment detected that lies along the density contour for @c threshold. +CP_EXPORT void cpMarchSoft( + cpBB bb, unsigned long x_samples, unsigned long y_samples, cpFloat threshold, + cpMarchSegmentFunc segment, void *segment_data, + cpMarchSampleFunc sample, void *sample_data +); + +/// Trace an aliased curve of an image along a particular threshold. +/// The given number of samples will be taken and spread across the bounding box area using the sampling function and context. +/// The segment function will be called for each segment detected that lies along the density contour for @c threshold. +CP_EXPORT void cpMarchHard( + cpBB bb, unsigned long x_samples, unsigned long y_samples, cpFloat threshold, + cpMarchSegmentFunc segment, void *segment_data, + cpMarchSampleFunc sample, void *sample_data +); diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPinJoint.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPinJoint.h" new file mode 100644 index 0000000..45aaa3e --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPinJoint.h" @@ -0,0 +1,50 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpPinJoint cpPinJoint +/// @{ + +/// Check if a constraint is a pin joint. +CP_EXPORT cpBool cpConstraintIsPinJoint(const cpConstraint *constraint); + +/// Allocate a pin joint. +CP_EXPORT cpPinJoint* cpPinJointAlloc(void); +/// Initialize a pin joint. +CP_EXPORT cpPinJoint* cpPinJointInit(cpPinJoint *joint, cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB); +/// Allocate and initialize a pin joint. +CP_EXPORT cpConstraint* cpPinJointNew(cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB); + +/// Get the location of the first anchor relative to the first body. +CP_EXPORT cpVect cpPinJointGetAnchorA(const cpConstraint *constraint); +/// Set the location of the first anchor relative to the first body. +CP_EXPORT void cpPinJointSetAnchorA(cpConstraint *constraint, cpVect anchorA); + +/// Get the location of the second anchor relative to the second body. +CP_EXPORT cpVect cpPinJointGetAnchorB(const cpConstraint *constraint); +/// Set the location of the second anchor relative to the second body. +CP_EXPORT void cpPinJointSetAnchorB(cpConstraint *constraint, cpVect anchorB); + +/// Get the distance the joint will maintain between the two anchors. +CP_EXPORT cpFloat cpPinJointGetDist(const cpConstraint *constraint); +/// Set the distance the joint will maintain between the two anchors. +CP_EXPORT void cpPinJointSetDist(cpConstraint *constraint, cpFloat dist); + +///@} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPivotJoint.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPivotJoint.h" new file mode 100644 index 0000000..4a620ef --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPivotJoint.h" @@ -0,0 +1,47 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpPivotJoint cpPivotJoint +/// @{ + +/// Check if a constraint is a slide joint. +CP_EXPORT cpBool cpConstraintIsPivotJoint(const cpConstraint *constraint); + +/// Allocate a pivot joint +CP_EXPORT cpPivotJoint* cpPivotJointAlloc(void); +/// Initialize a pivot joint. +CP_EXPORT cpPivotJoint* cpPivotJointInit(cpPivotJoint *joint, cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB); +/// Allocate and initialize a pivot joint. +CP_EXPORT cpConstraint* cpPivotJointNew(cpBody *a, cpBody *b, cpVect pivot); +/// Allocate and initialize a pivot joint with specific anchors. +CP_EXPORT cpConstraint* cpPivotJointNew2(cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB); + +/// Get the location of the first anchor relative to the first body. +CP_EXPORT cpVect cpPivotJointGetAnchorA(const cpConstraint *constraint); +/// Set the location of the first anchor relative to the first body. +CP_EXPORT void cpPivotJointSetAnchorA(cpConstraint *constraint, cpVect anchorA); + +/// Get the location of the second anchor relative to the second body. +CP_EXPORT cpVect cpPivotJointGetAnchorB(const cpConstraint *constraint); +/// Set the location of the second anchor relative to the second body. +CP_EXPORT void cpPivotJointSetAnchorB(cpConstraint *constraint, cpVect anchorB); + +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyShape.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyShape.h" new file mode 100644 index 0000000..25f688b --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyShape.h" @@ -0,0 +1,56 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpPolyShape cpPolyShape +/// @{ + +/// Allocate a polygon shape. +CP_EXPORT cpPolyShape* cpPolyShapeAlloc(void); +/// Initialize a polygon shape with rounded corners. +/// A convex hull will be created from the vertexes. +CP_EXPORT cpPolyShape* cpPolyShapeInit(cpPolyShape *poly, cpBody *body, int count, const cpVect *verts, cpTransform transform, cpFloat radius); +/// Initialize a polygon shape with rounded corners. +/// The vertexes must be convex with a counter-clockwise winding. +CP_EXPORT cpPolyShape* cpPolyShapeInitRaw(cpPolyShape *poly, cpBody *body, int count, const cpVect *verts, cpFloat radius); +/// Allocate and initialize a polygon shape with rounded corners. +/// A convex hull will be created from the vertexes. +CP_EXPORT cpShape* cpPolyShapeNew(cpBody *body, int count, const cpVect *verts, cpTransform transform, cpFloat radius); +/// Allocate and initialize a polygon shape with rounded corners. +/// The vertexes must be convex with a counter-clockwise winding. +CP_EXPORT cpShape* cpPolyShapeNewRaw(cpBody *body, int count, const cpVect *verts, cpFloat radius); + +/// Initialize a box shaped polygon shape with rounded corners. +CP_EXPORT cpPolyShape* cpBoxShapeInit(cpPolyShape *poly, cpBody *body, cpFloat width, cpFloat height, cpFloat radius); +/// Initialize an offset box shaped polygon shape with rounded corners. +CP_EXPORT cpPolyShape* cpBoxShapeInit2(cpPolyShape *poly, cpBody *body, cpBB box, cpFloat radius); +/// Allocate and initialize a box shaped polygon shape. +CP_EXPORT cpShape* cpBoxShapeNew(cpBody *body, cpFloat width, cpFloat height, cpFloat radius); +/// Allocate and initialize an offset box shaped polygon shape. +CP_EXPORT cpShape* cpBoxShapeNew2(cpBody *body, cpBB box, cpFloat radius); + +/// Get the number of verts in a polygon shape. +CP_EXPORT int cpPolyShapeGetCount(const cpShape *shape); +/// Get the @c ith vertex of a polygon shape. +CP_EXPORT cpVect cpPolyShapeGetVert(const cpShape *shape, int index); +/// Get the radius of a polygon shape. +CP_EXPORT cpFloat cpPolyShapeGetRadius(const cpShape *shape); + +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyline.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyline.h" new file mode 100644 index 0000000..4e878f0 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyline.h" @@ -0,0 +1,70 @@ +// Copyright 2013 Howling Moon Software. All rights reserved. +// See http://chipmunk2d.net/legal.php for more information. + +// Polylines are just arrays of vertexes. +// They are looped if the first vertex is equal to the last. +// cpPolyline structs are intended to be passed by value and destroyed when you are done with them. +typedef struct cpPolyline { + int count, capacity; + cpVect verts[]; +} cpPolyline; + +/// Destroy and free a polyline instance. +CP_EXPORT void cpPolylineFree(cpPolyline *line); + +/// Returns true if the first vertex is equal to the last. +CP_EXPORT cpBool cpPolylineIsClosed(cpPolyline *line); + +/** + Returns a copy of a polyline simplified by using the Douglas-Peucker algorithm. + This works very well on smooth or gently curved shapes, but not well on straight edged or angular shapes. +*/ +CP_EXPORT cpPolyline *cpPolylineSimplifyCurves(cpPolyline *line, cpFloat tol); + +/** + Returns a copy of a polyline simplified by discarding "flat" vertexes. + This works well on straigt edged or angular shapes, not as well on smooth shapes. +*/ +CP_EXPORT cpPolyline *cpPolylineSimplifyVertexes(cpPolyline *line, cpFloat tol); + +/// Get the convex hull of a polyline as a looped polyline. +CP_EXPORT cpPolyline *cpPolylineToConvexHull(cpPolyline *line, cpFloat tol); + + +/// Polyline sets are collections of polylines, generally built by cpMarchSoft() or cpMarchHard(). +typedef struct cpPolylineSet { + int count, capacity; + cpPolyline **lines; +} cpPolylineSet; + +/// Allocate a new polyline set. +CP_EXPORT cpPolylineSet *cpPolylineSetAlloc(void); + +/// Initialize a new polyline set. +CP_EXPORT cpPolylineSet *cpPolylineSetInit(cpPolylineSet *set); + +/// Allocate and initialize a polyline set. +CP_EXPORT cpPolylineSet *cpPolylineSetNew(void); + +/// Destroy a polyline set. +CP_EXPORT void cpPolylineSetDestroy(cpPolylineSet *set, cpBool freePolylines); + +/// Destroy and free a polyline set. +CP_EXPORT void cpPolylineSetFree(cpPolylineSet *set, cpBool freePolylines); + +/** + Add a line segment to a polyline set. + A segment will either start a new polyline, join two others, or add to or loop an existing polyline. + This is mostly intended to be used as a callback directly from cpMarchSoft() or cpMarchHard(). +*/ +CP_EXPORT void cpPolylineSetCollectSegment(cpVect v0, cpVect v1, cpPolylineSet *lines); + +/** + Get an approximate convex decomposition from a polyline. + Returns a cpPolylineSet of convex hulls that match the original shape to within 'tol'. + NOTE: If the input is a self intersecting polygon, the output might end up overly simplified. +*/ + +CP_EXPORT cpPolylineSet *cpPolylineConvexDecomposition(cpPolyline *line, cpFloat tol); + +#define cpPolylineConvexDecomposition_BETA cpPolylineConvexDecomposition diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRatchetJoint.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRatchetJoint.h" new file mode 100644 index 0000000..3ed4c91 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRatchetJoint.h" @@ -0,0 +1,50 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpRatchetJoint cpRatchetJoint +/// @{ + +/// Check if a constraint is a damped rotary springs. +CP_EXPORT cpBool cpConstraintIsRatchetJoint(const cpConstraint *constraint); + +/// Allocate a ratchet joint. +CP_EXPORT cpRatchetJoint* cpRatchetJointAlloc(void); +/// Initialize a ratched joint. +CP_EXPORT cpRatchetJoint* cpRatchetJointInit(cpRatchetJoint *joint, cpBody *a, cpBody *b, cpFloat phase, cpFloat ratchet); +/// Allocate and initialize a ratchet joint. +CP_EXPORT cpConstraint* cpRatchetJointNew(cpBody *a, cpBody *b, cpFloat phase, cpFloat ratchet); + +/// Get the angle of the current ratchet tooth. +CP_EXPORT cpFloat cpRatchetJointGetAngle(const cpConstraint *constraint); +/// Set the angle of the current ratchet tooth. +CP_EXPORT void cpRatchetJointSetAngle(cpConstraint *constraint, cpFloat angle); + +/// Get the phase offset of the ratchet. +CP_EXPORT cpFloat cpRatchetJointGetPhase(const cpConstraint *constraint); +/// Get the phase offset of the ratchet. +CP_EXPORT void cpRatchetJointSetPhase(cpConstraint *constraint, cpFloat phase); + +/// Get the angular distance of each ratchet. +CP_EXPORT cpFloat cpRatchetJointGetRatchet(const cpConstraint *constraint); +/// Set the angular distance of each ratchet. +CP_EXPORT void cpRatchetJointSetRatchet(cpConstraint *constraint, cpFloat ratchet); + +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRobust.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRobust.h" new file mode 100644 index 0000000..e4b2c42 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRobust.h" @@ -0,0 +1,11 @@ +#include "chipmunk/cpVect.h" + +// This is a private header for functions (currently just one) that need strict floating point results. +// It was easier to put this in it's own file than to fiddle with 4 different compiler specific pragmas or attributes. +// "Fast math" should be disabled here. + +// Check if c is to the left of segment (a, b). +cpBool cpCheckPointGreater(const cpVect a, const cpVect b, const cpVect c); + +// Check if p is behind one of v0 or v1 on axis n. +cpBool cpCheckAxis(cpVect v0, cpVect v1, cpVect p, cpVect n); diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRotaryLimitJoint.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRotaryLimitJoint.h" new file mode 100644 index 0000000..fac7ad8 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRotaryLimitJoint.h" @@ -0,0 +1,45 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpRotaryLimitJoint cpRotaryLimitJoint +/// @{ + +/// Check if a constraint is a damped rotary springs. +CP_EXPORT cpBool cpConstraintIsRotaryLimitJoint(const cpConstraint *constraint); + +/// Allocate a damped rotary limit joint. +CP_EXPORT cpRotaryLimitJoint* cpRotaryLimitJointAlloc(void); +/// Initialize a damped rotary limit joint. +CP_EXPORT cpRotaryLimitJoint* cpRotaryLimitJointInit(cpRotaryLimitJoint *joint, cpBody *a, cpBody *b, cpFloat min, cpFloat max); +/// Allocate and initialize a damped rotary limit joint. +CP_EXPORT cpConstraint* cpRotaryLimitJointNew(cpBody *a, cpBody *b, cpFloat min, cpFloat max); + +/// Get the minimum distance the joint will maintain between the two anchors. +CP_EXPORT cpFloat cpRotaryLimitJointGetMin(const cpConstraint *constraint); +/// Set the minimum distance the joint will maintain between the two anchors. +CP_EXPORT void cpRotaryLimitJointSetMin(cpConstraint *constraint, cpFloat min); + +/// Get the maximum distance the joint will maintain between the two anchors. +CP_EXPORT cpFloat cpRotaryLimitJointGetMax(const cpConstraint *constraint); +/// Set the maximum distance the joint will maintain between the two anchors. +CP_EXPORT void cpRotaryLimitJointSetMax(cpConstraint *constraint, cpFloat max); + +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpShape.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpShape.h" new file mode 100644 index 0000000..c78ed05 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpShape.h" @@ -0,0 +1,199 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpShape cpShape +/// The cpShape struct defines the shape of a rigid body. +/// @{ + +/// Point query info struct. +typedef struct cpPointQueryInfo { + /// The nearest shape, NULL if no shape was within range. + const cpShape *shape; + /// The closest point on the shape's surface. (in world space coordinates) + cpVect point; + /// The distance to the point. The distance is negative if the point is inside the shape. + cpFloat distance; + /// The gradient of the signed distance function. + /// The value should be similar to info.p/info.d, but accurate even for very small values of info.d. + cpVect gradient; +} cpPointQueryInfo; + +/// Segment query info struct. +typedef struct cpSegmentQueryInfo { + /// The shape that was hit, or NULL if no collision occured. + const cpShape *shape; + /// The point of impact. + cpVect point; + /// The normal of the surface hit. + cpVect normal; + /// The normalized distance along the query segment in the range [0, 1]. + cpFloat alpha; +} cpSegmentQueryInfo; + +/// Fast collision filtering type that is used to determine if two objects collide before calling collision or query callbacks. +typedef struct cpShapeFilter { + /// Two objects with the same non-zero group value do not collide. + /// This is generally used to group objects in a composite object together to disable self collisions. + cpGroup group; + /// A bitmask of user definable categories that this object belongs to. + /// The category/mask combinations of both objects in a collision must agree for a collision to occur. + cpBitmask categories; + /// A bitmask of user definable category types that this object object collides with. + /// The category/mask combinations of both objects in a collision must agree for a collision to occur. + cpBitmask mask; +} cpShapeFilter; + +/// Collision filter value for a shape that will collide with anything except CP_SHAPE_FILTER_NONE. +static const cpShapeFilter CP_SHAPE_FILTER_ALL = {CP_NO_GROUP, CP_ALL_CATEGORIES, CP_ALL_CATEGORIES}; +/// Collision filter value for a shape that does not collide with anything. +static const cpShapeFilter CP_SHAPE_FILTER_NONE = {CP_NO_GROUP, ~CP_ALL_CATEGORIES, ~CP_ALL_CATEGORIES}; + +/// Create a new collision filter. +static inline cpShapeFilter +cpShapeFilterNew(cpGroup group, cpBitmask categories, cpBitmask mask) +{ + cpShapeFilter filter = {group, categories, mask}; + return filter; +} + +/// Destroy a shape. +CP_EXPORT void cpShapeDestroy(cpShape *shape); +/// Destroy and Free a shape. +CP_EXPORT void cpShapeFree(cpShape *shape); + +/// Update, cache and return the bounding box of a shape based on the body it's attached to. +CP_EXPORT cpBB cpShapeCacheBB(cpShape *shape); +/// Update, cache and return the bounding box of a shape with an explicit transformation. +CP_EXPORT cpBB cpShapeUpdate(cpShape *shape, cpTransform transform); + +/// Perform a nearest point query. It finds the closest point on the surface of shape to a specific point. +/// The value returned is the distance between the points. A negative distance means the point is inside the shape. +CP_EXPORT cpFloat cpShapePointQuery(const cpShape *shape, cpVect p, cpPointQueryInfo *out); + +/// Perform a segment query against a shape. @c info must be a pointer to a valid cpSegmentQueryInfo structure. +CP_EXPORT cpBool cpShapeSegmentQuery(const cpShape *shape, cpVect a, cpVect b, cpFloat radius, cpSegmentQueryInfo *info); + +/// Return contact information about two shapes. +CP_EXPORT cpContactPointSet cpShapesCollide(const cpShape *a, const cpShape *b); + +/// The cpSpace this body is added to. +CP_EXPORT cpSpace* cpShapeGetSpace(const cpShape *shape); + +/// The cpBody this shape is connected to. +CP_EXPORT cpBody* cpShapeGetBody(const cpShape *shape); +/// Set the cpBody this shape is connected to. +/// Can only be used if the shape is not currently added to a space. +CP_EXPORT void cpShapeSetBody(cpShape *shape, cpBody *body); + +/// Get the mass of the shape if you are having Chipmunk calculate mass properties for you. +CP_EXPORT cpFloat cpShapeGetMass(cpShape *shape); +/// Set the mass of this shape to have Chipmunk calculate mass properties for you. +CP_EXPORT void cpShapeSetMass(cpShape *shape, cpFloat mass); + +/// Get the density of the shape if you are having Chipmunk calculate mass properties for you. +CP_EXPORT cpFloat cpShapeGetDensity(cpShape *shape); +/// Set the density of this shape to have Chipmunk calculate mass properties for you. +CP_EXPORT void cpShapeSetDensity(cpShape *shape, cpFloat density); + +/// Get the calculated moment of inertia for this shape. +CP_EXPORT cpFloat cpShapeGetMoment(cpShape *shape); +/// Get the calculated area of this shape. +CP_EXPORT cpFloat cpShapeGetArea(cpShape *shape); +/// Get the centroid of this shape. +CP_EXPORT cpVect cpShapeGetCenterOfGravity(cpShape *shape); + +/// Get the bounding box that contains the shape given it's current position and angle. +CP_EXPORT cpBB cpShapeGetBB(const cpShape *shape); + +/// Get if the shape is set to be a sensor or not. +CP_EXPORT cpBool cpShapeGetSensor(const cpShape *shape); +/// Set if the shape is a sensor or not. +CP_EXPORT void cpShapeSetSensor(cpShape *shape, cpBool sensor); + +/// Get the elasticity of this shape. +CP_EXPORT cpFloat cpShapeGetElasticity(const cpShape *shape); +/// Set the elasticity of this shape. +CP_EXPORT void cpShapeSetElasticity(cpShape *shape, cpFloat elasticity); + +/// Get the friction of this shape. +CP_EXPORT cpFloat cpShapeGetFriction(const cpShape *shape); +/// Set the friction of this shape. +CP_EXPORT void cpShapeSetFriction(cpShape *shape, cpFloat friction); + +/// Get the surface velocity of this shape. +CP_EXPORT cpVect cpShapeGetSurfaceVelocity(const cpShape *shape); +/// Set the surface velocity of this shape. +CP_EXPORT void cpShapeSetSurfaceVelocity(cpShape *shape, cpVect surfaceVelocity); + +/// Get the user definable data pointer of this shape. +CP_EXPORT cpDataPointer cpShapeGetUserData(const cpShape *shape); +/// Set the user definable data pointer of this shape. +CP_EXPORT void cpShapeSetUserData(cpShape *shape, cpDataPointer userData); + +/// Set the collision type of this shape. +CP_EXPORT cpCollisionType cpShapeGetCollisionType(const cpShape *shape); +/// Get the collision type of this shape. +CP_EXPORT void cpShapeSetCollisionType(cpShape *shape, cpCollisionType collisionType); + +/// Get the collision filtering parameters of this shape. +CP_EXPORT cpShapeFilter cpShapeGetFilter(const cpShape *shape); +/// Set the collision filtering parameters of this shape. +CP_EXPORT void cpShapeSetFilter(cpShape *shape, cpShapeFilter filter); + + +/// @} +/// @defgroup cpCircleShape cpCircleShape + +/// Allocate a circle shape. +CP_EXPORT cpCircleShape* cpCircleShapeAlloc(void); +/// Initialize a circle shape. +CP_EXPORT cpCircleShape* cpCircleShapeInit(cpCircleShape *circle, cpBody *body, cpFloat radius, cpVect offset); +/// Allocate and initialize a circle shape. +CP_EXPORT cpShape* cpCircleShapeNew(cpBody *body, cpFloat radius, cpVect offset); + +/// Get the offset of a circle shape. +CP_EXPORT cpVect cpCircleShapeGetOffset(const cpShape *shape); +/// Get the radius of a circle shape. +CP_EXPORT cpFloat cpCircleShapeGetRadius(const cpShape *shape); + +/// @} +/// @defgroup cpSegmentShape cpSegmentShape + +/// Allocate a segment shape. +CP_EXPORT cpSegmentShape* cpSegmentShapeAlloc(void); +/// Initialize a segment shape. +CP_EXPORT cpSegmentShape* cpSegmentShapeInit(cpSegmentShape *seg, cpBody *body, cpVect a, cpVect b, cpFloat radius); +/// Allocate and initialize a segment shape. +CP_EXPORT cpShape* cpSegmentShapeNew(cpBody *body, cpVect a, cpVect b, cpFloat radius); + +/// Let Chipmunk know about the geometry of adjacent segments to avoid colliding with endcaps. +CP_EXPORT void cpSegmentShapeSetNeighbors(cpShape *shape, cpVect prev, cpVect next); + +/// Get the first endpoint of a segment shape. +CP_EXPORT cpVect cpSegmentShapeGetA(const cpShape *shape); +/// Get the second endpoint of a segment shape. +CP_EXPORT cpVect cpSegmentShapeGetB(const cpShape *shape); +/// Get the normal of a segment shape. +CP_EXPORT cpVect cpSegmentShapeGetNormal(const cpShape *shape); +/// Get the first endpoint of a segment shape. +CP_EXPORT cpFloat cpSegmentShapeGetRadius(const cpShape *shape); + +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSimpleMotor.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSimpleMotor.h" new file mode 100644 index 0000000..811b011 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSimpleMotor.h" @@ -0,0 +1,43 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpSimpleMotor cpSimpleMotor +/// @{ + +/// Opaque struct type for damped rotary springs. +typedef struct cpSimpleMotor cpSimpleMotor; + +/// Check if a constraint is a damped rotary springs. +CP_EXPORT cpBool cpConstraintIsSimpleMotor(const cpConstraint *constraint); + +/// Allocate a simple motor. +CP_EXPORT cpSimpleMotor* cpSimpleMotorAlloc(void); +/// initialize a simple motor. +CP_EXPORT cpSimpleMotor* cpSimpleMotorInit(cpSimpleMotor *joint, cpBody *a, cpBody *b, cpFloat rate); +/// Allocate and initialize a simple motor. +CP_EXPORT cpConstraint* cpSimpleMotorNew(cpBody *a, cpBody *b, cpFloat rate); + +/// Get the rate of the motor. +CP_EXPORT cpFloat cpSimpleMotorGetRate(const cpConstraint *constraint); +/// Set the rate of the motor. +CP_EXPORT void cpSimpleMotorSetRate(cpConstraint *constraint, cpFloat rate); + +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSlideJoint.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSlideJoint.h" new file mode 100644 index 0000000..c41f9a4 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSlideJoint.h" @@ -0,0 +1,55 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpSlideJoint cpSlideJoint +/// @{ + +/// Check if a constraint is a slide joint. +CP_EXPORT cpBool cpConstraintIsSlideJoint(const cpConstraint *constraint); + +/// Allocate a slide joint. +CP_EXPORT cpSlideJoint* cpSlideJointAlloc(void); +/// Initialize a slide joint. +CP_EXPORT cpSlideJoint* cpSlideJointInit(cpSlideJoint *joint, cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB, cpFloat min, cpFloat max); +/// Allocate and initialize a slide joint. +CP_EXPORT cpConstraint* cpSlideJointNew(cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB, cpFloat min, cpFloat max); + +/// Get the location of the first anchor relative to the first body. +CP_EXPORT cpVect cpSlideJointGetAnchorA(const cpConstraint *constraint); +/// Set the location of the first anchor relative to the first body. +CP_EXPORT void cpSlideJointSetAnchorA(cpConstraint *constraint, cpVect anchorA); + +/// Get the location of the second anchor relative to the second body. +CP_EXPORT cpVect cpSlideJointGetAnchorB(const cpConstraint *constraint); +/// Set the location of the second anchor relative to the second body. +CP_EXPORT void cpSlideJointSetAnchorB(cpConstraint *constraint, cpVect anchorB); + +/// Get the minimum distance the joint will maintain between the two anchors. +CP_EXPORT cpFloat cpSlideJointGetMin(const cpConstraint *constraint); +/// Set the minimum distance the joint will maintain between the two anchors. +CP_EXPORT void cpSlideJointSetMin(cpConstraint *constraint, cpFloat min); + +/// Get the maximum distance the joint will maintain between the two anchors. +CP_EXPORT cpFloat cpSlideJointGetMax(const cpConstraint *constraint); +/// Set the maximum distance the joint will maintain between the two anchors. +CP_EXPORT void cpSlideJointSetMax(cpConstraint *constraint, cpFloat max); + +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpace.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpace.h" new file mode 100644 index 0000000..7bbabb8 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpace.h" @@ -0,0 +1,319 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/// @defgroup cpSpace cpSpace +/// @{ + +//MARK: Definitions + +/// Collision begin event function callback type. +/// Returning false from a begin callback causes the collision to be ignored until +/// the the separate callback is called when the objects stop colliding. +typedef cpBool (*cpCollisionBeginFunc)(cpArbiter *arb, cpSpace *space, cpDataPointer userData); +/// Collision pre-solve event function callback type. +/// Returning false from a pre-step callback causes the collision to be ignored until the next step. +typedef cpBool (*cpCollisionPreSolveFunc)(cpArbiter *arb, cpSpace *space, cpDataPointer userData); +/// Collision post-solve event function callback type. +typedef void (*cpCollisionPostSolveFunc)(cpArbiter *arb, cpSpace *space, cpDataPointer userData); +/// Collision separate event function callback type. +typedef void (*cpCollisionSeparateFunc)(cpArbiter *arb, cpSpace *space, cpDataPointer userData); + +/// Struct that holds function callback pointers to configure custom collision handling. +/// Collision handlers have a pair of types; when a collision occurs between two shapes that have these types, the collision handler functions are triggered. +struct cpCollisionHandler { + /// Collision type identifier of the first shape that this handler recognizes. + /// In the collision handler callback, the shape with this type will be the first argument. Read only. + const cpCollisionType typeA; + /// Collision type identifier of the second shape that this handler recognizes. + /// In the collision handler callback, the shape with this type will be the second argument. Read only. + const cpCollisionType typeB; + /// This function is called when two shapes with types that match this collision handler begin colliding. + cpCollisionBeginFunc beginFunc; + /// This function is called each step when two shapes with types that match this collision handler are colliding. + /// It's called before the collision solver runs so that you can affect a collision's outcome. + cpCollisionPreSolveFunc preSolveFunc; + /// This function is called each step when two shapes with types that match this collision handler are colliding. + /// It's called after the collision solver runs so that you can read back information about the collision to trigger events in your game. + cpCollisionPostSolveFunc postSolveFunc; + /// This function is called when two shapes with types that match this collision handler stop colliding. + cpCollisionSeparateFunc separateFunc; + /// This is a user definable context pointer that is passed to all of the collision handler functions. + cpDataPointer userData; +}; + +// TODO: Make timestep a parameter? + + +//MARK: Memory and Initialization + +/// Allocate a cpSpace. +CP_EXPORT cpSpace* cpSpaceAlloc(void); +/// Initialize a cpSpace. +CP_EXPORT cpSpace* cpSpaceInit(cpSpace *space); +/// Allocate and initialize a cpSpace. +CP_EXPORT cpSpace* cpSpaceNew(void); + +/// Destroy a cpSpace. +CP_EXPORT void cpSpaceDestroy(cpSpace *space); +/// Destroy and free a cpSpace. +CP_EXPORT void cpSpaceFree(cpSpace *space); + + +//MARK: Properties + +/// Number of iterations to use in the impulse solver to solve contacts and other constraints. +CP_EXPORT int cpSpaceGetIterations(const cpSpace *space); +CP_EXPORT void cpSpaceSetIterations(cpSpace *space, int iterations); + +/// Gravity to pass to rigid bodies when integrating velocity. +CP_EXPORT cpVect cpSpaceGetGravity(const cpSpace *space); +CP_EXPORT void cpSpaceSetGravity(cpSpace *space, cpVect gravity); + +/// Damping rate expressed as the fraction of velocity bodies retain each second. +/// A value of 0.9 would mean that each body's velocity will drop 10% per second. +/// The default value is 1.0, meaning no damping is applied. +/// @note This damping value is different than those of cpDampedSpring and cpDampedRotarySpring. +CP_EXPORT cpFloat cpSpaceGetDamping(const cpSpace *space); +CP_EXPORT void cpSpaceSetDamping(cpSpace *space, cpFloat damping); + +/// Speed threshold for a body to be considered idle. +/// The default value of 0 means to let the space guess a good threshold based on gravity. +CP_EXPORT cpFloat cpSpaceGetIdleSpeedThreshold(const cpSpace *space); +CP_EXPORT void cpSpaceSetIdleSpeedThreshold(cpSpace *space, cpFloat idleSpeedThreshold); + +/// Time a group of bodies must remain idle in order to fall asleep. +/// Enabling sleeping also implicitly enables the the contact graph. +/// The default value of INFINITY disables the sleeping algorithm. +CP_EXPORT cpFloat cpSpaceGetSleepTimeThreshold(const cpSpace *space); +CP_EXPORT void cpSpaceSetSleepTimeThreshold(cpSpace *space, cpFloat sleepTimeThreshold); + +/// Amount of encouraged penetration between colliding shapes. +/// Used to reduce oscillating contacts and keep the collision cache warm. +/// Defaults to 0.1. If you have poor simulation quality, +/// increase this number as much as possible without allowing visible amounts of overlap. +CP_EXPORT cpFloat cpSpaceGetCollisionSlop(const cpSpace *space); +CP_EXPORT void cpSpaceSetCollisionSlop(cpSpace *space, cpFloat collisionSlop); + +/// Determines how fast overlapping shapes are pushed apart. +/// Expressed as a fraction of the error remaining after each second. +/// Defaults to pow(1.0 - 0.1, 60.0) meaning that Chipmunk fixes 10% of overlap each frame at 60Hz. +CP_EXPORT cpFloat cpSpaceGetCollisionBias(const cpSpace *space); +CP_EXPORT void cpSpaceSetCollisionBias(cpSpace *space, cpFloat collisionBias); + +/// Number of frames that contact information should persist. +/// Defaults to 3. There is probably never a reason to change this value. +CP_EXPORT cpTimestamp cpSpaceGetCollisionPersistence(const cpSpace *space); +CP_EXPORT void cpSpaceSetCollisionPersistence(cpSpace *space, cpTimestamp collisionPersistence); + +/// User definable data pointer. +/// Generally this points to your game's controller or game state +/// class so you can access it when given a cpSpace reference in a callback. +CP_EXPORT cpDataPointer cpSpaceGetUserData(const cpSpace *space); +CP_EXPORT void cpSpaceSetUserData(cpSpace *space, cpDataPointer userData); + +/// The Space provided static body for a given cpSpace. +/// This is merely provided for convenience and you are not required to use it. +CP_EXPORT cpBody* cpSpaceGetStaticBody(const cpSpace *space); + +/// Returns the current (or most recent) time step used with the given space. +/// Useful from callbacks if your time step is not a compile-time global. +CP_EXPORT cpFloat cpSpaceGetCurrentTimeStep(const cpSpace *space); + +/// returns true from inside a callback when objects cannot be added/removed. +CP_EXPORT cpBool cpSpaceIsLocked(cpSpace *space); + + +//MARK: Collision Handlers + +/// Create or return the existing collision handler that is called for all collisions that are not handled by a more specific collision handler. +CP_EXPORT cpCollisionHandler *cpSpaceAddDefaultCollisionHandler(cpSpace *space); +/// Create or return the existing collision handler for the specified pair of collision types. +/// If wildcard handlers are used with either of the collision types, it's the responibility of the custom handler to invoke the wildcard handlers. +CP_EXPORT cpCollisionHandler *cpSpaceAddCollisionHandler(cpSpace *space, cpCollisionType a, cpCollisionType b); +/// Create or return the existing wildcard collision handler for the specified type. +CP_EXPORT cpCollisionHandler *cpSpaceAddWildcardHandler(cpSpace *space, cpCollisionType type); + + +//MARK: Add/Remove objects + +/// Add a collision shape to the simulation. +/// If the shape is attached to a static body, it will be added as a static shape. +CP_EXPORT cpShape* cpSpaceAddShape(cpSpace *space, cpShape *shape); +/// Add a rigid body to the simulation. +CP_EXPORT cpBody* cpSpaceAddBody(cpSpace *space, cpBody *body); +/// Add a constraint to the simulation. +CP_EXPORT cpConstraint* cpSpaceAddConstraint(cpSpace *space, cpConstraint *constraint); + +/// Remove a collision shape from the simulation. +CP_EXPORT void cpSpaceRemoveShape(cpSpace *space, cpShape *shape); +/// Remove a rigid body from the simulation. +CP_EXPORT void cpSpaceRemoveBody(cpSpace *space, cpBody *body); +/// Remove a constraint from the simulation. +CP_EXPORT void cpSpaceRemoveConstraint(cpSpace *space, cpConstraint *constraint); + +/// Test if a collision shape has been added to the space. +CP_EXPORT cpBool cpSpaceContainsShape(cpSpace *space, cpShape *shape); +/// Test if a rigid body has been added to the space. +CP_EXPORT cpBool cpSpaceContainsBody(cpSpace *space, cpBody *body); +/// Test if a constraint has been added to the space. +CP_EXPORT cpBool cpSpaceContainsConstraint(cpSpace *space, cpConstraint *constraint); + +//MARK: Post-Step Callbacks + +/// Post Step callback function type. +typedef void (*cpPostStepFunc)(cpSpace *space, void *key, void *data); +/// Schedule a post-step callback to be called when cpSpaceStep() finishes. +/// You can only register one callback per unique value for @c key. +/// Returns true only if @c key has never been scheduled before. +/// It's possible to pass @c NULL for @c func if you only want to mark @c key as being used. +CP_EXPORT cpBool cpSpaceAddPostStepCallback(cpSpace *space, cpPostStepFunc func, void *key, void *data); + + +//MARK: Queries + +// TODO: Queries and iterators should take a cpSpace parametery. +// TODO: They should also be abortable. + +/// Nearest point query callback function type. +typedef void (*cpSpacePointQueryFunc)(cpShape *shape, cpVect point, cpFloat distance, cpVect gradient, void *data); +/// Query the space at a point and call @c func for each shape found. +CP_EXPORT void cpSpacePointQuery(cpSpace *space, cpVect point, cpFloat maxDistance, cpShapeFilter filter, cpSpacePointQueryFunc func, void *data); +/// Query the space at a point and return the nearest shape found. Returns NULL if no shapes were found. +CP_EXPORT cpShape *cpSpacePointQueryNearest(cpSpace *space, cpVect point, cpFloat maxDistance, cpShapeFilter filter, cpPointQueryInfo *out); + +/// Segment query callback function type. +typedef void (*cpSpaceSegmentQueryFunc)(cpShape *shape, cpVect point, cpVect normal, cpFloat alpha, void *data); +/// Perform a directed line segment query (like a raycast) against the space calling @c func for each shape intersected. +CP_EXPORT void cpSpaceSegmentQuery(cpSpace *space, cpVect start, cpVect end, cpFloat radius, cpShapeFilter filter, cpSpaceSegmentQueryFunc func, void *data); +/// Perform a directed line segment query (like a raycast) against the space and return the first shape hit. Returns NULL if no shapes were hit. +CP_EXPORT cpShape *cpSpaceSegmentQueryFirst(cpSpace *space, cpVect start, cpVect end, cpFloat radius, cpShapeFilter filter, cpSegmentQueryInfo *out); + +/// Rectangle Query callback function type. +typedef void (*cpSpaceBBQueryFunc)(cpShape *shape, void *data); +/// Perform a fast rectangle query on the space calling @c func for each shape found. +/// Only the shape's bounding boxes are checked for overlap, not their full shape. +CP_EXPORT void cpSpaceBBQuery(cpSpace *space, cpBB bb, cpShapeFilter filter, cpSpaceBBQueryFunc func, void *data); + +/// Shape query callback function type. +typedef void (*cpSpaceShapeQueryFunc)(cpShape *shape, cpContactPointSet *points, void *data); +/// Query a space for any shapes overlapping the given shape and call @c func for each shape found. +CP_EXPORT cpBool cpSpaceShapeQuery(cpSpace *space, cpShape *shape, cpSpaceShapeQueryFunc func, void *data); + + +//MARK: Iteration + +/// Space/body iterator callback function type. +typedef void (*cpSpaceBodyIteratorFunc)(cpBody *body, void *data); +/// Call @c func for each body in the space. +CP_EXPORT void cpSpaceEachBody(cpSpace *space, cpSpaceBodyIteratorFunc func, void *data); + +/// Space/body iterator callback function type. +typedef void (*cpSpaceShapeIteratorFunc)(cpShape *shape, void *data); +/// Call @c func for each shape in the space. +CP_EXPORT void cpSpaceEachShape(cpSpace *space, cpSpaceShapeIteratorFunc func, void *data); + +/// Space/constraint iterator callback function type. +typedef void (*cpSpaceConstraintIteratorFunc)(cpConstraint *constraint, void *data); +/// Call @c func for each shape in the space. +CP_EXPORT void cpSpaceEachConstraint(cpSpace *space, cpSpaceConstraintIteratorFunc func, void *data); + + +//MARK: Indexing + +/// Update the collision detection info for the static shapes in the space. +CP_EXPORT void cpSpaceReindexStatic(cpSpace *space); +/// Update the collision detection data for a specific shape in the space. +CP_EXPORT void cpSpaceReindexShape(cpSpace *space, cpShape *shape); +/// Update the collision detection data for all shapes attached to a body. +CP_EXPORT void cpSpaceReindexShapesForBody(cpSpace *space, cpBody *body); + +/// Switch the space to use a spatial has as it's spatial index. +CP_EXPORT void cpSpaceUseSpatialHash(cpSpace *space, cpFloat dim, int count); + + +//MARK: Time Stepping + +/// Step the space forward in time by @c dt. +CP_EXPORT void cpSpaceStep(cpSpace *space, cpFloat dt); + + +//MARK: Debug API + +#ifndef CP_SPACE_DISABLE_DEBUG_API + +/// Color type to use with the space debug drawing API. +typedef struct cpSpaceDebugColor { + float r, g, b, a; +} cpSpaceDebugColor; + +/// Callback type for a function that draws a filled, stroked circle. +typedef void (*cpSpaceDebugDrawCircleImpl)(cpVect pos, cpFloat angle, cpFloat radius, cpSpaceDebugColor outlineColor, cpSpaceDebugColor fillColor, cpDataPointer data); +/// Callback type for a function that draws a line segment. +typedef void (*cpSpaceDebugDrawSegmentImpl)(cpVect a, cpVect b, cpSpaceDebugColor color, cpDataPointer data); +/// Callback type for a function that draws a thick line segment. +typedef void (*cpSpaceDebugDrawFatSegmentImpl)(cpVect a, cpVect b, cpFloat radius, cpSpaceDebugColor outlineColor, cpSpaceDebugColor fillColor, cpDataPointer data); +/// Callback type for a function that draws a convex polygon. +typedef void (*cpSpaceDebugDrawPolygonImpl)(int count, const cpVect *verts, cpFloat radius, cpSpaceDebugColor outlineColor, cpSpaceDebugColor fillColor, cpDataPointer data); +/// Callback type for a function that draws a dot. +typedef void (*cpSpaceDebugDrawDotImpl)(cpFloat size, cpVect pos, cpSpaceDebugColor color, cpDataPointer data); +/// Callback type for a function that returns a color for a given shape. This gives you an opportunity to color shapes based on how they are used in your engine. +typedef cpSpaceDebugColor (*cpSpaceDebugDrawColorForShapeImpl)(cpShape *shape, cpDataPointer data); + +typedef enum cpSpaceDebugDrawFlags { + CP_SPACE_DEBUG_DRAW_SHAPES = 1<<0, + CP_SPACE_DEBUG_DRAW_CONSTRAINTS = 1<<1, + CP_SPACE_DEBUG_DRAW_COLLISION_POINTS = 1<<2, +} cpSpaceDebugDrawFlags; + +/// Struct used with cpSpaceDebugDraw() containing drawing callbacks and other drawing settings. +typedef struct cpSpaceDebugDrawOptions { + /// Function that will be invoked to draw circles. + cpSpaceDebugDrawCircleImpl drawCircle; + /// Function that will be invoked to draw line segments. + cpSpaceDebugDrawSegmentImpl drawSegment; + /// Function that will be invoked to draw thick line segments. + cpSpaceDebugDrawFatSegmentImpl drawFatSegment; + /// Function that will be invoked to draw convex polygons. + cpSpaceDebugDrawPolygonImpl drawPolygon; + /// Function that will be invoked to draw dots. + cpSpaceDebugDrawDotImpl drawDot; + + /// Flags that request which things to draw (collision shapes, constraints, contact points). + cpSpaceDebugDrawFlags flags; + /// Outline color passed to the drawing function. + cpSpaceDebugColor shapeOutlineColor; + /// Function that decides what fill color to draw shapes using. + cpSpaceDebugDrawColorForShapeImpl colorForShape; + /// Color passed to drawing functions for constraints. + cpSpaceDebugColor constraintColor; + /// Color passed to drawing functions for collision points. + cpSpaceDebugColor collisionPointColor; + + /// User defined context pointer passed to all of the callback functions as the 'data' argument. + cpDataPointer data; +} cpSpaceDebugDrawOptions; + +/// Debug draw the current state of the space using the supplied drawing options. +CP_EXPORT void cpSpaceDebugDraw(cpSpace *space, cpSpaceDebugDrawOptions *options); + +#endif + +/// @} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpatialIndex.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpatialIndex.h" new file mode 100644 index 0000000..1f7c68c --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpatialIndex.h" @@ -0,0 +1,227 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +/** + @defgroup cpSpatialIndex cpSpatialIndex + + Spatial indexes are data structures that are used to accelerate collision detection + and spatial queries. Chipmunk provides a number of spatial index algorithms to pick from + and they are programmed in a generic way so that you can use them for holding more than + just cpShape structs. + + It works by using @c void pointers to the objects you add and using a callback to ask your code + for bounding boxes when it needs them. Several types of queries can be performed an index as well + as reindexing and full collision information. All communication to the spatial indexes is performed + through callback functions. + + Spatial indexes should be treated as opaque structs. + This meanns you shouldn't be reading any of the struct fields. + @{ +*/ + +//MARK: Spatial Index + +/// Spatial index bounding box callback function type. +/// The spatial index calls this function and passes you a pointer to an object you added +/// when it needs to get the bounding box associated with that object. +typedef cpBB (*cpSpatialIndexBBFunc)(void *obj); +/// Spatial index/object iterator callback function type. +typedef void (*cpSpatialIndexIteratorFunc)(void *obj, void *data); +/// Spatial query callback function type. +typedef cpCollisionID (*cpSpatialIndexQueryFunc)(void *obj1, void *obj2, cpCollisionID id, void *data); +/// Spatial segment query callback function type. +typedef cpFloat (*cpSpatialIndexSegmentQueryFunc)(void *obj1, void *obj2, void *data); + + +typedef struct cpSpatialIndexClass cpSpatialIndexClass; +typedef struct cpSpatialIndex cpSpatialIndex; + +/// @private +struct cpSpatialIndex { + cpSpatialIndexClass *klass; + + cpSpatialIndexBBFunc bbfunc; + + cpSpatialIndex *staticIndex, *dynamicIndex; +}; + + +//MARK: Spatial Hash + +typedef struct cpSpaceHash cpSpaceHash; + +/// Allocate a spatial hash. +CP_EXPORT cpSpaceHash* cpSpaceHashAlloc(void); +/// Initialize a spatial hash. +CP_EXPORT cpSpatialIndex* cpSpaceHashInit(cpSpaceHash *hash, cpFloat celldim, int numcells, cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex); +/// Allocate and initialize a spatial hash. +CP_EXPORT cpSpatialIndex* cpSpaceHashNew(cpFloat celldim, int cells, cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex); + +/// Change the cell dimensions and table size of the spatial hash to tune it. +/// The cell dimensions should roughly match the average size of your objects +/// and the table size should be ~10 larger than the number of objects inserted. +/// Some trial and error is required to find the optimum numbers for efficiency. +CP_EXPORT void cpSpaceHashResize(cpSpaceHash *hash, cpFloat celldim, int numcells); + +//MARK: AABB Tree + +typedef struct cpBBTree cpBBTree; + +/// Allocate a bounding box tree. +CP_EXPORT cpBBTree* cpBBTreeAlloc(void); +/// Initialize a bounding box tree. +CP_EXPORT cpSpatialIndex* cpBBTreeInit(cpBBTree *tree, cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex); +/// Allocate and initialize a bounding box tree. +CP_EXPORT cpSpatialIndex* cpBBTreeNew(cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex); + +/// Perform a static top down optimization of the tree. +CP_EXPORT void cpBBTreeOptimize(cpSpatialIndex *index); + +/// Bounding box tree velocity callback function. +/// This function should return an estimate for the object's velocity. +typedef cpVect (*cpBBTreeVelocityFunc)(void *obj); +/// Set the velocity function for the bounding box tree to enable temporal coherence. +CP_EXPORT void cpBBTreeSetVelocityFunc(cpSpatialIndex *index, cpBBTreeVelocityFunc func); + +//MARK: Single Axis Sweep + +typedef struct cpSweep1D cpSweep1D; + +/// Allocate a 1D sort and sweep broadphase. +CP_EXPORT cpSweep1D* cpSweep1DAlloc(void); +/// Initialize a 1D sort and sweep broadphase. +CP_EXPORT cpSpatialIndex* cpSweep1DInit(cpSweep1D *sweep, cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex); +/// Allocate and initialize a 1D sort and sweep broadphase. +CP_EXPORT cpSpatialIndex* cpSweep1DNew(cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex); + +//MARK: Spatial Index Implementation + +typedef void (*cpSpatialIndexDestroyImpl)(cpSpatialIndex *index); + +typedef int (*cpSpatialIndexCountImpl)(cpSpatialIndex *index); +typedef void (*cpSpatialIndexEachImpl)(cpSpatialIndex *index, cpSpatialIndexIteratorFunc func, void *data); + +typedef cpBool (*cpSpatialIndexContainsImpl)(cpSpatialIndex *index, void *obj, cpHashValue hashid); +typedef void (*cpSpatialIndexInsertImpl)(cpSpatialIndex *index, void *obj, cpHashValue hashid); +typedef void (*cpSpatialIndexRemoveImpl)(cpSpatialIndex *index, void *obj, cpHashValue hashid); + +typedef void (*cpSpatialIndexReindexImpl)(cpSpatialIndex *index); +typedef void (*cpSpatialIndexReindexObjectImpl)(cpSpatialIndex *index, void *obj, cpHashValue hashid); +typedef void (*cpSpatialIndexReindexQueryImpl)(cpSpatialIndex *index, cpSpatialIndexQueryFunc func, void *data); + +typedef void (*cpSpatialIndexQueryImpl)(cpSpatialIndex *index, void *obj, cpBB bb, cpSpatialIndexQueryFunc func, void *data); +typedef void (*cpSpatialIndexSegmentQueryImpl)(cpSpatialIndex *index, void *obj, cpVect a, cpVect b, cpFloat t_exit, cpSpatialIndexSegmentQueryFunc func, void *data); + +struct cpSpatialIndexClass { + cpSpatialIndexDestroyImpl destroy; + + cpSpatialIndexCountImpl count; + cpSpatialIndexEachImpl each; + + cpSpatialIndexContainsImpl contains; + cpSpatialIndexInsertImpl insert; + cpSpatialIndexRemoveImpl remove; + + cpSpatialIndexReindexImpl reindex; + cpSpatialIndexReindexObjectImpl reindexObject; + cpSpatialIndexReindexQueryImpl reindexQuery; + + cpSpatialIndexQueryImpl query; + cpSpatialIndexSegmentQueryImpl segmentQuery; +}; + +/// Destroy and free a spatial index. +CP_EXPORT void cpSpatialIndexFree(cpSpatialIndex *index); +/// Collide the objects in @c dynamicIndex against the objects in @c staticIndex using the query callback function. +CP_EXPORT void cpSpatialIndexCollideStatic(cpSpatialIndex *dynamicIndex, cpSpatialIndex *staticIndex, cpSpatialIndexQueryFunc func, void *data); + +/// Destroy a spatial index. +static inline void cpSpatialIndexDestroy(cpSpatialIndex *index) +{ + if(index->klass) index->klass->destroy(index); +} + +/// Get the number of objects in the spatial index. +static inline int cpSpatialIndexCount(cpSpatialIndex *index) +{ + return index->klass->count(index); +} + +/// Iterate the objects in the spatial index. @c func will be called once for each object. +static inline void cpSpatialIndexEach(cpSpatialIndex *index, cpSpatialIndexIteratorFunc func, void *data) +{ + index->klass->each(index, func, data); +} + +/// Returns true if the spatial index contains the given object. +/// Most spatial indexes use hashed storage, so you must provide a hash value too. +static inline cpBool cpSpatialIndexContains(cpSpatialIndex *index, void *obj, cpHashValue hashid) +{ + return index->klass->contains(index, obj, hashid); +} + +/// Add an object to a spatial index. +/// Most spatial indexes use hashed storage, so you must provide a hash value too. +static inline void cpSpatialIndexInsert(cpSpatialIndex *index, void *obj, cpHashValue hashid) +{ + index->klass->insert(index, obj, hashid); +} + +/// Remove an object from a spatial index. +/// Most spatial indexes use hashed storage, so you must provide a hash value too. +static inline void cpSpatialIndexRemove(cpSpatialIndex *index, void *obj, cpHashValue hashid) +{ + index->klass->remove(index, obj, hashid); +} + +/// Perform a full reindex of a spatial index. +static inline void cpSpatialIndexReindex(cpSpatialIndex *index) +{ + index->klass->reindex(index); +} + +/// Reindex a single object in the spatial index. +static inline void cpSpatialIndexReindexObject(cpSpatialIndex *index, void *obj, cpHashValue hashid) +{ + index->klass->reindexObject(index, obj, hashid); +} + +/// Perform a rectangle query against the spatial index, calling @c func for each potential match. +static inline void cpSpatialIndexQuery(cpSpatialIndex *index, void *obj, cpBB bb, cpSpatialIndexQueryFunc func, void *data) +{ + index->klass->query(index, obj, bb, func, data); +} + +/// Perform a segment query against the spatial index, calling @c func for each potential match. +static inline void cpSpatialIndexSegmentQuery(cpSpatialIndex *index, void *obj, cpVect a, cpVect b, cpFloat t_exit, cpSpatialIndexSegmentQueryFunc func, void *data) +{ + index->klass->segmentQuery(index, obj, a, b, t_exit, func, data); +} + +/// Simultaneously reindex and find all colliding objects. +/// @c func will be called once for each potentially overlapping pair of objects found. +/// If the spatial index was initialized with a static index, it will collide it's objects against that as well. +static inline void cpSpatialIndexReindexQuery(cpSpatialIndex *index, cpSpatialIndexQueryFunc func, void *data) +{ + index->klass->reindexQuery(index, func, data); +} + +///@} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpTransform.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpTransform.h" new file mode 100644 index 0000000..4a6256b --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpTransform.h" @@ -0,0 +1,198 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#ifndef CHIPMUNK_TRANSFORM_H +#define CHIPMUNK_TRANSFORM_H + +#include "chipmunk_types.h" +#include "cpVect.h" +#include "cpBB.h" + +/// Identity transform matrix. +static const cpTransform cpTransformIdentity = {1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}; + +/// Construct a new transform matrix. +/// (a, b) is the x basis vector. +/// (c, d) is the y basis vector. +/// (tx, ty) is the translation. +static inline cpTransform +cpTransformNew(cpFloat a, cpFloat b, cpFloat c, cpFloat d, cpFloat tx, cpFloat ty) +{ + cpTransform t = {a, b, c, d, tx, ty}; + return t; +} + +/// Construct a new transform matrix in transposed order. +static inline cpTransform +cpTransformNewTranspose(cpFloat a, cpFloat c, cpFloat tx, cpFloat b, cpFloat d, cpFloat ty) +{ + cpTransform t = {a, b, c, d, tx, ty}; + return t; +} + +/// Get the inverse of a transform matrix. +static inline cpTransform +cpTransformInverse(cpTransform t) +{ + cpFloat inv_det = 1.0/(t.a*t.d - t.c*t.b); + return cpTransformNewTranspose( + t.d*inv_det, -t.c*inv_det, (t.c*t.ty - t.tx*t.d)*inv_det, + -t.b*inv_det, t.a*inv_det, (t.tx*t.b - t.a*t.ty)*inv_det + ); +} + +/// Multiply two transformation matrices. +static inline cpTransform +cpTransformMult(cpTransform t1, cpTransform t2) +{ + return cpTransformNewTranspose( + t1.a*t2.a + t1.c*t2.b, t1.a*t2.c + t1.c*t2.d, t1.a*t2.tx + t1.c*t2.ty + t1.tx, + t1.b*t2.a + t1.d*t2.b, t1.b*t2.c + t1.d*t2.d, t1.b*t2.tx + t1.d*t2.ty + t1.ty + ); +} + +/// Transform an absolute point. (i.e. a vertex) +static inline cpVect +cpTransformPoint(cpTransform t, cpVect p) +{ + return cpv(t.a*p.x + t.c*p.y + t.tx, t.b*p.x + t.d*p.y + t.ty); +} + +/// Transform a vector (i.e. a normal) +static inline cpVect +cpTransformVect(cpTransform t, cpVect v) +{ + return cpv(t.a*v.x + t.c*v.y, t.b*v.x + t.d*v.y); +} + +/// Transform a cpBB. +static inline cpBB +cpTransformbBB(cpTransform t, cpBB bb) +{ + cpVect center = cpBBCenter(bb); + cpFloat hw = (bb.r - bb.l)*0.5; + cpFloat hh = (bb.t - bb.b)*0.5; + + cpFloat a = t.a*hw, b = t.c*hh, d = t.b*hw, e = t.d*hh; + cpFloat hw_max = cpfmax(cpfabs(a + b), cpfabs(a - b)); + cpFloat hh_max = cpfmax(cpfabs(d + e), cpfabs(d - e)); + return cpBBNewForExtents(cpTransformPoint(t, center), hw_max, hh_max); +} + +/// Create a transation matrix. +static inline cpTransform +cpTransformTranslate(cpVect translate) +{ + return cpTransformNewTranspose( + 1.0, 0.0, translate.x, + 0.0, 1.0, translate.y + ); +} + +/// Create a scale matrix. +static inline cpTransform +cpTransformScale(cpFloat scaleX, cpFloat scaleY) +{ + return cpTransformNewTranspose( + scaleX, 0.0, 0.0, + 0.0, scaleY, 0.0 + ); +} + +/// Create a rotation matrix. +static inline cpTransform +cpTransformRotate(cpFloat radians) +{ + cpVect rot = cpvforangle(radians); + return cpTransformNewTranspose( + rot.x, -rot.y, 0.0, + rot.y, rot.x, 0.0 + ); +} + +/// Create a rigid transformation matrix. (transation + rotation) +static inline cpTransform +cpTransformRigid(cpVect translate, cpFloat radians) +{ + cpVect rot = cpvforangle(radians); + return cpTransformNewTranspose( + rot.x, -rot.y, translate.x, + rot.y, rot.x, translate.y + ); +} + +/// Fast inverse of a rigid transformation matrix. +static inline cpTransform +cpTransformRigidInverse(cpTransform t) +{ + return cpTransformNewTranspose( + t.d, -t.c, (t.c*t.ty - t.tx*t.d), + -t.b, t.a, (t.tx*t.b - t.a*t.ty) + ); +} + +//MARK: Miscellaneous (but useful) transformation matrices. +// See source for documentation... + +static inline cpTransform +cpTransformWrap(cpTransform outer, cpTransform inner) +{ + return cpTransformMult(cpTransformInverse(outer), cpTransformMult(inner, outer)); +} + +static inline cpTransform +cpTransformWrapInverse(cpTransform outer, cpTransform inner) +{ + return cpTransformMult(outer, cpTransformMult(inner, cpTransformInverse(outer))); +} + +static inline cpTransform +cpTransformOrtho(cpBB bb) +{ + return cpTransformNewTranspose( + 2.0/(bb.r - bb.l), 0.0, -(bb.r + bb.l)/(bb.r - bb.l), + 0.0, 2.0/(bb.t - bb.b), -(bb.t + bb.b)/(bb.t - bb.b) + ); +} + +static inline cpTransform +cpTransformBoneScale(cpVect v0, cpVect v1) +{ + cpVect d = cpvsub(v1, v0); + return cpTransformNewTranspose( + d.x, -d.y, v0.x, + d.y, d.x, v0.y + ); +} + +static inline cpTransform +cpTransformAxialScale(cpVect axis, cpVect pivot, cpFloat scale) +{ + cpFloat A = axis.x*axis.y*(scale - 1.0); + cpFloat B = cpvdot(axis, pivot)*(1.0 - scale); + + return cpTransformNewTranspose( + scale*axis.x*axis.x + axis.y*axis.y, A, axis.x*B, + A, axis.x*axis.x + scale*axis.y*axis.y, axis.y*B + ); +} + +#endif diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpVect.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpVect.h" new file mode 100644 index 0000000..8ec02bd --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpVect.h" @@ -0,0 +1,230 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#ifndef CHIPMUNK_VECT_H +#define CHIPMUNK_VECT_H + +#include "chipmunk_types.h" + +/// @defgroup cpVect cpVect +/// Chipmunk's 2D vector type along with a handy 2D vector math lib. +/// @{ + +/// Constant for the zero vector. +static const cpVect cpvzero = {0.0f,0.0f}; + +/// Convenience constructor for cpVect structs. +static inline cpVect cpv(const cpFloat x, const cpFloat y) +{ + cpVect v = {x, y}; + return v; +} + +/// Check if two vectors are equal. (Be careful when comparing floating point numbers!) +static inline cpBool cpveql(const cpVect v1, const cpVect v2) +{ + return (v1.x == v2.x && v1.y == v2.y); +} + +/// Add two vectors +static inline cpVect cpvadd(const cpVect v1, const cpVect v2) +{ + return cpv(v1.x + v2.x, v1.y + v2.y); +} + +/// Subtract two vectors. +static inline cpVect cpvsub(const cpVect v1, const cpVect v2) +{ + return cpv(v1.x - v2.x, v1.y - v2.y); +} + +/// Negate a vector. +static inline cpVect cpvneg(const cpVect v) +{ + return cpv(-v.x, -v.y); +} + +/// Scalar multiplication. +static inline cpVect cpvmult(const cpVect v, const cpFloat s) +{ + return cpv(v.x*s, v.y*s); +} + +/// Vector dot product. +static inline cpFloat cpvdot(const cpVect v1, const cpVect v2) +{ + return v1.x*v2.x + v1.y*v2.y; +} + +/// 2D vector cross product analog. +/// The cross product of 2D vectors results in a 3D vector with only a z component. +/// This function returns the magnitude of the z value. +static inline cpFloat cpvcross(const cpVect v1, const cpVect v2) +{ + return v1.x*v2.y - v1.y*v2.x; +} + +/// Returns a perpendicular vector. (90 degree rotation) +static inline cpVect cpvperp(const cpVect v) +{ + return cpv(-v.y, v.x); +} + +/// Returns a perpendicular vector. (-90 degree rotation) +static inline cpVect cpvrperp(const cpVect v) +{ + return cpv(v.y, -v.x); +} + +/// Returns the vector projection of v1 onto v2. +static inline cpVect cpvproject(const cpVect v1, const cpVect v2) +{ + return cpvmult(v2, cpvdot(v1, v2)/cpvdot(v2, v2)); +} + +/// Returns the unit length vector for the given angle (in radians). +static inline cpVect cpvforangle(const cpFloat a) +{ + return cpv(cpfcos(a), cpfsin(a)); +} + +/// Returns the angular direction v is pointing in (in radians). +static inline cpFloat cpvtoangle(const cpVect v) +{ + return cpfatan2(v.y, v.x); +} + +/// Uses complex number multiplication to rotate v1 by v2. Scaling will occur if v1 is not a unit vector. +static inline cpVect cpvrotate(const cpVect v1, const cpVect v2) +{ + return cpv(v1.x*v2.x - v1.y*v2.y, v1.x*v2.y + v1.y*v2.x); +} + +/// Inverse of cpvrotate(). +static inline cpVect cpvunrotate(const cpVect v1, const cpVect v2) +{ + return cpv(v1.x*v2.x + v1.y*v2.y, v1.y*v2.x - v1.x*v2.y); +} + +/// Returns the squared length of v. Faster than cpvlength() when you only need to compare lengths. +static inline cpFloat cpvlengthsq(const cpVect v) +{ + return cpvdot(v, v); +} + +/// Returns the length of v. +static inline cpFloat cpvlength(const cpVect v) +{ + return cpfsqrt(cpvdot(v, v)); +} + +/// Linearly interpolate between v1 and v2. +static inline cpVect cpvlerp(const cpVect v1, const cpVect v2, const cpFloat t) +{ + return cpvadd(cpvmult(v1, 1.0f - t), cpvmult(v2, t)); +} + +/// Returns a normalized copy of v. +static inline cpVect cpvnormalize(const cpVect v) +{ + // Neat trick I saw somewhere to avoid div/0. + return cpvmult(v, 1.0f/(cpvlength(v) + CPFLOAT_MIN)); +} + +/// Spherical linearly interpolate between v1 and v2. +static inline cpVect +cpvslerp(const cpVect v1, const cpVect v2, const cpFloat t) +{ + cpFloat dot = cpvdot(cpvnormalize(v1), cpvnormalize(v2)); + cpFloat omega = cpfacos(cpfclamp(dot, -1.0f, 1.0f)); + + if(omega < 1e-3){ + // If the angle between two vectors is very small, lerp instead to avoid precision issues. + return cpvlerp(v1, v2, t); + } else { + cpFloat denom = 1.0f/cpfsin(omega); + return cpvadd(cpvmult(v1, cpfsin((1.0f - t)*omega)*denom), cpvmult(v2, cpfsin(t*omega)*denom)); + } +} + +/// Spherical linearly interpolate between v1 towards v2 by no more than angle a radians +static inline cpVect +cpvslerpconst(const cpVect v1, const cpVect v2, const cpFloat a) +{ + cpFloat dot = cpvdot(cpvnormalize(v1), cpvnormalize(v2)); + cpFloat omega = cpfacos(cpfclamp(dot, -1.0f, 1.0f)); + + return cpvslerp(v1, v2, cpfmin(a, omega)/omega); +} + +/// Clamp v to length len. +static inline cpVect cpvclamp(const cpVect v, const cpFloat len) +{ + return (cpvdot(v,v) > len*len) ? cpvmult(cpvnormalize(v), len) : v; +} + +/// Linearly interpolate between v1 towards v2 by distance d. +static inline cpVect cpvlerpconst(cpVect v1, cpVect v2, cpFloat d) +{ + return cpvadd(v1, cpvclamp(cpvsub(v2, v1), d)); +} + +/// Returns the distance between v1 and v2. +static inline cpFloat cpvdist(const cpVect v1, const cpVect v2) +{ + return cpvlength(cpvsub(v1, v2)); +} + +/// Returns the squared distance between v1 and v2. Faster than cpvdist() when you only need to compare distances. +static inline cpFloat cpvdistsq(const cpVect v1, const cpVect v2) +{ + return cpvlengthsq(cpvsub(v1, v2)); +} + +/// Returns true if the distance between v1 and v2 is less than dist. +static inline cpBool cpvnear(const cpVect v1, const cpVect v2, const cpFloat dist) +{ + return cpvdistsq(v1, v2) < dist*dist; +} + +/// @} + +/// @defgroup cpMat2x2 cpMat2x2 +/// 2x2 matrix type used for tensors and such. +/// @{ + +// NUKE +static inline cpMat2x2 +cpMat2x2New(cpFloat a, cpFloat b, cpFloat c, cpFloat d) +{ + cpMat2x2 m = {a, b, c, d}; + return m; +} + +static inline cpVect +cpMat2x2Transform(cpMat2x2 m, cpVect v) +{ + return cpv(v.x*m.a + v.y*m.b, v.x*m.c + v.y*m.d); +} + +///@} + +#endif diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/chipmunk.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/chipmunk.c" new file mode 100644 index 0000000..c6477e1 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/chipmunk.c" @@ -0,0 +1,331 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include +#include +#include +#if defined(ANDROID) +# include +#endif + +#include "chipmunk/chipmunk_private.h" + +void +cpMessage(const char *condition, const char *file, int line, int isError, int isHardError, const char *message, ...) +{ + fprintf(stderr, (isError ? "Aborting due to Chipmunk error: " : "Chipmunk warning: ")); + + va_list vargs; + va_start(vargs, message); { +#if defined(ANDROID) + __android_log_print( ANDROID_LOG_INFO, "Chipmunk", "%s(%d)", file, line ); + __android_log_print( ANDROID_LOG_INFO, "Chipmunk", message, vargs ); +#else + vfprintf(stderr, message, vargs); + fprintf(stderr, "\n"); +#endif + } va_end(vargs); + +#if defined(ANDROID) + __android_log_print(ANDROID_LOG_INFO, "Chipmunk", "\tFailed condition: %s\n", condition); + __android_log_print(ANDROID_LOG_INFO, "Chipmunk", "\tSource:%s:%d\n", file, line); +#else + fprintf(stderr, "\tFailed condition: %s\n", condition); + fprintf(stderr, "\tSource:%s:%d\n", file, line); +#endif +} + +#define STR(s) #s +#define XSTR(s) STR(s) + +const char *cpVersionString = XSTR(CP_VERSION_MAJOR)"."XSTR(CP_VERSION_MINOR)"."XSTR(CP_VERSION_RELEASE); + +//MARK: Misc Functions + +cpFloat +cpMomentForCircle(cpFloat m, cpFloat r1, cpFloat r2, cpVect offset) +{ + return m*(0.5f*(r1*r1 + r2*r2) + cpvlengthsq(offset)); +} + +cpFloat +cpAreaForCircle(cpFloat r1, cpFloat r2) +{ + return (cpFloat)CP_PI*cpfabs(r1*r1 - r2*r2); +} + +cpFloat +cpMomentForSegment(cpFloat m, cpVect a, cpVect b, cpFloat r) +{ + cpVect offset = cpvlerp(a, b, 0.5f); + + // This approximates the shape as a box for rounded segments, but it's quite close. + cpFloat length = cpvdist(b, a) + 2.0f*r; + return m*((length*length + 4.0f*r*r)/12.0f + cpvlengthsq(offset)); +} + +cpFloat +cpAreaForSegment(cpVect a, cpVect b, cpFloat r) +{ + return r*((cpFloat)CP_PI*r + 2.0f*cpvdist(a, b)); +} + +cpFloat +cpMomentForPoly(cpFloat m, const int count, const cpVect *verts, cpVect offset, cpFloat r) +{ + // TODO account for radius. + if(count == 2) return cpMomentForSegment(m, verts[0], verts[1], 0.0f); + + cpFloat sum1 = 0.0f; + cpFloat sum2 = 0.0f; + for(int i=0; i max.x || (v.x == max.x && v.y > max.y)){ + max = v; + (*end) = i; + } + } +} + +#define SWAP(__A__, __B__) {cpVect __TMP__ = __A__; __A__ = __B__; __B__ = __TMP__;} + +static int +QHullPartition(cpVect *verts, int count, cpVect a, cpVect b, cpFloat tol) +{ + if(count == 0) return 0; + + cpFloat max = 0; + int pivot = 0; + + cpVect delta = cpvsub(b, a); + cpFloat valueTol = tol*cpvlength(delta); + + int head = 0; + for(int tail = count-1; head <= tail;){ + cpFloat value = cpvcross(cpvsub(verts[head], a), delta); + if(value > valueTol){ + if(value > max){ + max = value; + pivot = head; + } + + head++; + } else { + SWAP(verts[head], verts[tail]); + tail--; + } + } + + // move the new pivot to the front if it's not already there. + if(pivot != 0) SWAP(verts[0], verts[pivot]); + return head; +} + +static int +QHullReduce(cpFloat tol, cpVect *verts, int count, cpVect a, cpVect pivot, cpVect b, cpVect *result) +{ + if(count < 0){ + return 0; + } else if(count == 0) { + result[0] = pivot; + return 1; + } else { + int left_count = QHullPartition(verts, count, a, pivot, tol); + int index = QHullReduce(tol, verts + 1, left_count - 1, a, verts[0], pivot, result); + + result[index++] = pivot; + + int right_count = QHullPartition(verts + left_count, count - left_count, pivot, b, tol); + return index + QHullReduce(tol, verts + left_count + 1, right_count - 1, pivot, verts[left_count], b, result + index); + } +} + +// QuickHull seemed like a neat algorithm, and efficient-ish for large input sets. +// My implementation performs an in place reduction using the result array as scratch space. +int +cpConvexHull(int count, const cpVect *verts, cpVect *result, int *first, cpFloat tol) +{ + if(verts != result){ + // Copy the line vertexes into the empty part of the result polyline to use as a scratch buffer. + memcpy(result, verts, count*sizeof(cpVect)); + } + + // Degenerate case, all points are the same. + int start, end; + cpLoopIndexes(verts, count, &start, &end); + if(start == end){ + if(first) (*first) = 0; + return 1; + } + + SWAP(result[0], result[start]); + SWAP(result[1], result[end == 0 ? start : end]); + + cpVect a = result[0]; + cpVect b = result[1]; + + if(first) (*first) = start; + return QHullReduce(tol, result + 2, count - 2, a, b, a, result + 1) + 1; +} + +//MARK: Alternate Block Iterators + +#if defined(__has_extension) +#if __has_extension(blocks) + +static void IteratorFunc(void *ptr, void (^block)(void *ptr)){block(ptr);} + +void cpSpaceEachBody_b(cpSpace *space, void (^block)(cpBody *body)){ + cpSpaceEachBody(space, (cpSpaceBodyIteratorFunc)IteratorFunc, block); +} + +void cpSpaceEachShape_b(cpSpace *space, void (^block)(cpShape *shape)){ + cpSpaceEachShape(space, (cpSpaceShapeIteratorFunc)IteratorFunc, block); +} + +void cpSpaceEachConstraint_b(cpSpace *space, void (^block)(cpConstraint *constraint)){ + cpSpaceEachConstraint(space, (cpSpaceConstraintIteratorFunc)IteratorFunc, block); +} + +static void BodyIteratorFunc(cpBody *body, void *ptr, void (^block)(void *ptr)){block(ptr);} + +void cpBodyEachShape_b(cpBody *body, void (^block)(cpShape *shape)){ + cpBodyEachShape(body, (cpBodyShapeIteratorFunc)BodyIteratorFunc, block); +} + +void cpBodyEachConstraint_b(cpBody *body, void (^block)(cpConstraint *constraint)){ + cpBodyEachConstraint(body, (cpBodyConstraintIteratorFunc)BodyIteratorFunc, block); +} + +void cpBodyEachArbiter_b(cpBody *body, void (^block)(cpArbiter *arbiter)){ + cpBodyEachArbiter(body, (cpBodyArbiterIteratorFunc)BodyIteratorFunc, block); +} + +static void PointQueryIteratorFunc(cpShape *shape, cpVect p, cpFloat d, cpVect g, cpSpacePointQueryBlock block){block(shape, p, d, g);} +void cpSpacePointQuery_b(cpSpace *space, cpVect point, cpFloat maxDistance, cpShapeFilter filter, cpSpacePointQueryBlock block){ + cpSpacePointQuery(space, point, maxDistance, filter, (cpSpacePointQueryFunc)PointQueryIteratorFunc, block); +} + +static void SegmentQueryIteratorFunc(cpShape *shape, cpVect p, cpVect n, cpFloat t, cpSpaceSegmentQueryBlock block){block(shape, p, n, t);} +void cpSpaceSegmentQuery_b(cpSpace *space, cpVect start, cpVect end, cpFloat radius, cpShapeFilter filter, cpSpaceSegmentQueryBlock block){ + cpSpaceSegmentQuery(space, start, end, radius, filter, (cpSpaceSegmentQueryFunc)SegmentQueryIteratorFunc, block); +} + +void cpSpaceBBQuery_b(cpSpace *space, cpBB bb, cpShapeFilter filter, cpSpaceBBQueryBlock block){ + cpSpaceBBQuery(space, bb, filter, (cpSpaceBBQueryFunc)IteratorFunc, block); +} + +static void ShapeQueryIteratorFunc(cpShape *shape, cpContactPointSet *points, cpSpaceShapeQueryBlock block){block(shape, points);} +cpBool cpSpaceShapeQuery_b(cpSpace *space, cpShape *shape, cpSpaceShapeQueryBlock block){ + return cpSpaceShapeQuery(space, shape, (cpSpaceShapeQueryFunc)ShapeQueryIteratorFunc, block); +} + +#endif +#endif + +#include "chipmunk/chipmunk_ffi.h" diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArbiter.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArbiter.c" new file mode 100644 index 0000000..6c52cee --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArbiter.c" @@ -0,0 +1,496 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +// TODO: make this generic so I can reuse it for constraints also. +static inline void +unthreadHelper(cpArbiter *arb, cpBody *body) +{ + struct cpArbiterThread *thread = cpArbiterThreadForBody(arb, body); + cpArbiter *prev = thread->prev; + cpArbiter *next = thread->next; + + if(prev){ + cpArbiterThreadForBody(prev, body)->next = next; + } else if(body->arbiterList == arb) { + // IFF prev is NULL and body->arbiterList == arb, is arb at the head of the list. + // This function may be called for an arbiter that was never in a list. + // In that case, we need to protect it from wiping out the body->arbiterList pointer. + body->arbiterList = next; + } + + if(next) cpArbiterThreadForBody(next, body)->prev = prev; + + thread->prev = NULL; + thread->next = NULL; +} + +void +cpArbiterUnthread(cpArbiter *arb) +{ + unthreadHelper(arb, arb->body_a); + unthreadHelper(arb, arb->body_b); +} + +cpBool cpArbiterIsFirstContact(const cpArbiter *arb) +{ + return arb->state == CP_ARBITER_STATE_FIRST_COLLISION; +} + +cpBool cpArbiterIsRemoval(const cpArbiter *arb) +{ + return arb->state == CP_ARBITER_STATE_INVALIDATED; +} + +int cpArbiterGetCount(const cpArbiter *arb) +{ + // Return 0 contacts if we are in a separate callback. + return (arb->state < CP_ARBITER_STATE_CACHED ? arb->count : 0); +} + +cpVect +cpArbiterGetNormal(const cpArbiter *arb) +{ + return cpvmult(arb->n, arb->swapped ? -1.0f : 1.0); +} + +cpVect +cpArbiterGetPointA(const cpArbiter *arb, int i) +{ + cpAssertHard(0 <= i && i < cpArbiterGetCount(arb), "Index error: The specified contact index is invalid for this arbiter"); + return cpvadd(arb->body_a->p, arb->contacts[i].r1); +} + +cpVect +cpArbiterGetPointB(const cpArbiter *arb, int i) +{ + cpAssertHard(0 <= i && i < cpArbiterGetCount(arb), "Index error: The specified contact index is invalid for this arbiter"); + return cpvadd(arb->body_b->p, arb->contacts[i].r2); +} + +cpFloat +cpArbiterGetDepth(const cpArbiter *arb, int i) +{ + cpAssertHard(0 <= i && i < cpArbiterGetCount(arb), "Index error: The specified contact index is invalid for this arbiter"); + + struct cpContact *con = &arb->contacts[i]; + return cpvdot(cpvadd(cpvsub(con->r2, con->r1), cpvsub(arb->body_b->p, arb->body_a->p)), arb->n); +} + +cpContactPointSet +cpArbiterGetContactPointSet(const cpArbiter *arb) +{ + cpContactPointSet set; + set.count = cpArbiterGetCount(arb); + + cpBool swapped = arb->swapped; + cpVect n = arb->n; + set.normal = (swapped ? cpvneg(n) : n); + + for(int i=0; ibody_a->p, arb->contacts[i].r1); + cpVect p2 = cpvadd(arb->body_b->p, arb->contacts[i].r2); + + set.points[i].pointA = (swapped ? p2 : p1); + set.points[i].pointB = (swapped ? p1 : p2); + set.points[i].distance = cpvdot(cpvsub(p2, p1), n); + } + + return set; +} + +void +cpArbiterSetContactPointSet(cpArbiter *arb, cpContactPointSet *set) +{ + int count = set->count; + cpAssertHard(count == arb->count, "The number of contact points cannot be changed."); + + cpBool swapped = arb->swapped; + arb->n = (swapped ? cpvneg(set->normal) : set->normal); + + for(int i=0; ipoints[i].pointA; + cpVect p2 = set->points[i].pointB; + + arb->contacts[i].r1 = cpvsub(swapped ? p2 : p1, arb->body_a->p); + arb->contacts[i].r2 = cpvsub(swapped ? p1 : p2, arb->body_b->p); + } +} + +cpVect +cpArbiterTotalImpulse(const cpArbiter *arb) +{ + struct cpContact *contacts = arb->contacts; + cpVect n = arb->n; + cpVect sum = cpvzero; + + for(int i=0, count=cpArbiterGetCount(arb); ijnAcc, con->jtAcc))); + } + + return (arb->swapped ? sum : cpvneg(sum)); + return cpvzero; +} + +cpFloat +cpArbiterTotalKE(const cpArbiter *arb) +{ + cpFloat eCoef = (1 - arb->e)/(1 + arb->e); + cpFloat sum = 0.0; + + struct cpContact *contacts = arb->contacts; + for(int i=0, count=cpArbiterGetCount(arb); ijnAcc; + cpFloat jtAcc = con->jtAcc; + + sum += eCoef*jnAcc*jnAcc/con->nMass + jtAcc*jtAcc/con->tMass; + } + + return sum; +} + +cpBool +cpArbiterIgnore(cpArbiter *arb) +{ + arb->state = CP_ARBITER_STATE_IGNORE; + return cpFalse; +} + +cpFloat +cpArbiterGetRestitution(const cpArbiter *arb) +{ + return arb->e; +} + +void +cpArbiterSetRestitution(cpArbiter *arb, cpFloat restitution) +{ + arb->e = restitution; +} + +cpFloat +cpArbiterGetFriction(const cpArbiter *arb) +{ + return arb->u; +} + +void +cpArbiterSetFriction(cpArbiter *arb, cpFloat friction) +{ + arb->u = friction; +} + +cpVect +cpArbiterGetSurfaceVelocity(cpArbiter *arb) +{ + return cpvmult(arb->surface_vr, arb->swapped ? -1.0f : 1.0); +} + +void +cpArbiterSetSurfaceVelocity(cpArbiter *arb, cpVect vr) +{ + arb->surface_vr = cpvmult(vr, arb->swapped ? -1.0f : 1.0); +} + +cpDataPointer +cpArbiterGetUserData(const cpArbiter *arb) +{ + return arb->data; +} + +void +cpArbiterSetUserData(cpArbiter *arb, cpDataPointer userData) +{ + arb->data = userData; +} + +void +cpArbiterGetShapes(const cpArbiter *arb, cpShape **a, cpShape **b) +{ + if(arb->swapped){ + (*a) = (cpShape *)arb->b, (*b) = (cpShape *)arb->a; + } else { + (*a) = (cpShape *)arb->a, (*b) = (cpShape *)arb->b; + } +} + +void cpArbiterGetBodies(const cpArbiter *arb, cpBody **a, cpBody **b) +{ + CP_ARBITER_GET_SHAPES(arb, shape_a, shape_b); + (*a) = shape_a->body; + (*b) = shape_b->body; +} + +cpBool +cpArbiterCallWildcardBeginA(cpArbiter *arb, cpSpace *space) +{ + cpCollisionHandler *handler = arb->handlerA; + return handler->beginFunc(arb, space, handler->userData); +} + +cpBool +cpArbiterCallWildcardBeginB(cpArbiter *arb, cpSpace *space) +{ + cpCollisionHandler *handler = arb->handlerB; + arb->swapped = !arb->swapped; + cpBool retval = handler->beginFunc(arb, space, handler->userData); + arb->swapped = !arb->swapped; + return retval; +} + +cpBool +cpArbiterCallWildcardPreSolveA(cpArbiter *arb, cpSpace *space) +{ + cpCollisionHandler *handler = arb->handlerA; + return handler->preSolveFunc(arb, space, handler->userData); +} + +cpBool +cpArbiterCallWildcardPreSolveB(cpArbiter *arb, cpSpace *space) +{ + cpCollisionHandler *handler = arb->handlerB; + arb->swapped = !arb->swapped; + cpBool retval = handler->preSolveFunc(arb, space, handler->userData); + arb->swapped = !arb->swapped; + return retval; +} + +void +cpArbiterCallWildcardPostSolveA(cpArbiter *arb, cpSpace *space) +{ + cpCollisionHandler *handler = arb->handlerA; + handler->postSolveFunc(arb, space, handler->userData); +} + +void +cpArbiterCallWildcardPostSolveB(cpArbiter *arb, cpSpace *space) +{ + cpCollisionHandler *handler = arb->handlerB; + arb->swapped = !arb->swapped; + handler->postSolveFunc(arb, space, handler->userData); + arb->swapped = !arb->swapped; +} + +void +cpArbiterCallWildcardSeparateA(cpArbiter *arb, cpSpace *space) +{ + cpCollisionHandler *handler = arb->handlerA; + handler->separateFunc(arb, space, handler->userData); +} + +void +cpArbiterCallWildcardSeparateB(cpArbiter *arb, cpSpace *space) +{ + cpCollisionHandler *handler = arb->handlerB; + arb->swapped = !arb->swapped; + handler->separateFunc(arb, space, handler->userData); + arb->swapped = !arb->swapped; +} + +cpArbiter* +cpArbiterInit(cpArbiter *arb, cpShape *a, cpShape *b) +{ + arb->handler = NULL; + arb->swapped = cpFalse; + + arb->handler = NULL; + arb->handlerA = NULL; + arb->handlerB = NULL; + + arb->e = 0.0f; + arb->u = 0.0f; + arb->surface_vr = cpvzero; + + arb->count = 0; + arb->contacts = NULL; + + arb->a = a; arb->body_a = a->body; + arb->b = b; arb->body_b = b->body; + + arb->thread_a.next = NULL; + arb->thread_b.next = NULL; + arb->thread_a.prev = NULL; + arb->thread_b.prev = NULL; + + arb->stamp = 0; + arb->state = CP_ARBITER_STATE_FIRST_COLLISION; + + arb->data = NULL; + + return arb; +} + +static inline cpCollisionHandler * +cpSpaceLookupHandler(cpSpace *space, cpCollisionType a, cpCollisionType b, cpCollisionHandler *defaultValue) +{ + cpCollisionType types[] = {a, b}; + cpCollisionHandler *handler = (cpCollisionHandler *)cpHashSetFind(space->collisionHandlers, CP_HASH_PAIR(a, b), types); + return (handler ? handler : defaultValue); +} + +void +cpArbiterUpdate(cpArbiter *arb, struct cpCollisionInfo *info, cpSpace *space) +{ + const cpShape *a = info->a, *b = info->b; + + // For collisions between two similar primitive types, the order could have been swapped since the last frame. + arb->a = a; arb->body_a = a->body; + arb->b = b; arb->body_b = b->body; + + // Iterate over the possible pairs to look for hash value matches. + for(int i=0; icount; i++){ + struct cpContact *con = &info->arr[i]; + + // r1 and r2 store absolute offsets at init time. + // Need to convert them to relative offsets. + con->r1 = cpvsub(con->r1, a->body->p); + con->r2 = cpvsub(con->r2, b->body->p); + + // Cached impulses are not zeroed at init time. + con->jnAcc = con->jtAcc = 0.0f; + + for(int j=0; jcount; j++){ + struct cpContact *old = &arb->contacts[j]; + + // This could trigger false positives, but is fairly unlikely nor serious if it does. + if(con->hash == old->hash){ + // Copy the persistant contact information. + con->jnAcc = old->jnAcc; + con->jtAcc = old->jtAcc; + } + } + } + + arb->contacts = info->arr; + arb->count = info->count; + arb->n = info->n; + + arb->e = a->e * b->e; + arb->u = a->u * b->u; + + cpVect surface_vr = cpvsub(b->surfaceV, a->surfaceV); + arb->surface_vr = cpvsub(surface_vr, cpvmult(info->n, cpvdot(surface_vr, info->n))); + + cpCollisionType typeA = info->a->type, typeB = info->b->type; + cpCollisionHandler *defaultHandler = &space->defaultHandler; + cpCollisionHandler *handler = arb->handler = cpSpaceLookupHandler(space, typeA, typeB, defaultHandler); + + // Check if the types match, but don't swap for a default handler which use the wildcard for type A. + cpBool swapped = arb->swapped = (typeA != handler->typeA && handler->typeA != CP_WILDCARD_COLLISION_TYPE); + + if(handler != defaultHandler || space->usesWildcards){ + // The order of the main handler swaps the wildcard handlers too. Uffda. + arb->handlerA = cpSpaceLookupHandler(space, (swapped ? typeB : typeA), CP_WILDCARD_COLLISION_TYPE, &cpCollisionHandlerDoNothing); + arb->handlerB = cpSpaceLookupHandler(space, (swapped ? typeA : typeB), CP_WILDCARD_COLLISION_TYPE, &cpCollisionHandlerDoNothing); + } + + // mark it as new if it's been cached + if(arb->state == CP_ARBITER_STATE_CACHED) arb->state = CP_ARBITER_STATE_FIRST_COLLISION; +} + +void +cpArbiterPreStep(cpArbiter *arb, cpFloat dt, cpFloat slop, cpFloat bias) +{ + cpBody *a = arb->body_a; + cpBody *b = arb->body_b; + cpVect n = arb->n; + cpVect body_delta = cpvsub(b->p, a->p); + + for(int i=0; icount; i++){ + struct cpContact *con = &arb->contacts[i]; + + // Calculate the mass normal and mass tangent. + con->nMass = 1.0f/k_scalar(a, b, con->r1, con->r2, n); + con->tMass = 1.0f/k_scalar(a, b, con->r1, con->r2, cpvperp(n)); + + // Calculate the target bias velocity. + cpFloat dist = cpvdot(cpvadd(cpvsub(con->r2, con->r1), body_delta), n); + con->bias = -bias*cpfmin(0.0f, dist + slop)/dt; + con->jBias = 0.0f; + + // Calculate the target bounce velocity. + con->bounce = normal_relative_velocity(a, b, con->r1, con->r2, n)*arb->e; + } +} + +void +cpArbiterApplyCachedImpulse(cpArbiter *arb, cpFloat dt_coef) +{ + if(cpArbiterIsFirstContact(arb)) return; + + cpBody *a = arb->body_a; + cpBody *b = arb->body_b; + cpVect n = arb->n; + + for(int i=0; icount; i++){ + struct cpContact *con = &arb->contacts[i]; + cpVect j = cpvrotate(n, cpv(con->jnAcc, con->jtAcc)); + apply_impulses(a, b, con->r1, con->r2, cpvmult(j, dt_coef)); + } +} + +// TODO: is it worth splitting velocity/position correction? + +void +cpArbiterApplyImpulse(cpArbiter *arb) +{ + cpBody *a = arb->body_a; + cpBody *b = arb->body_b; + cpVect n = arb->n; + cpVect surface_vr = arb->surface_vr; + cpFloat friction = arb->u; + + for(int i=0; icount; i++){ + struct cpContact *con = &arb->contacts[i]; + cpFloat nMass = con->nMass; + cpVect r1 = con->r1; + cpVect r2 = con->r2; + + cpVect vb1 = cpvadd(a->v_bias, cpvmult(cpvperp(r1), a->w_bias)); + cpVect vb2 = cpvadd(b->v_bias, cpvmult(cpvperp(r2), b->w_bias)); + cpVect vr = cpvadd(relative_velocity(a, b, r1, r2), surface_vr); + + cpFloat vbn = cpvdot(cpvsub(vb2, vb1), n); + cpFloat vrn = cpvdot(vr, n); + cpFloat vrt = cpvdot(vr, cpvperp(n)); + + cpFloat jbn = (con->bias - vbn)*nMass; + cpFloat jbnOld = con->jBias; + con->jBias = cpfmax(jbnOld + jbn, 0.0f); + + cpFloat jn = -(con->bounce + vrn)*nMass; + cpFloat jnOld = con->jnAcc; + con->jnAcc = cpfmax(jnOld + jn, 0.0f); + + cpFloat jtMax = friction*con->jnAcc; + cpFloat jt = -vrt*con->tMass; + cpFloat jtOld = con->jtAcc; + con->jtAcc = cpfclamp(jtOld + jt, -jtMax, jtMax); + + apply_bias_impulses(a, b, r1, r2, cpvmult(n, con->jBias - jbnOld)); + apply_impulses(a, b, r1, r2, cpvrotate(n, cpv(con->jnAcc - jnOld, con->jtAcc - jtOld))); + } +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArray.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArray.c" new file mode 100644 index 0000000..a1f8df5 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArray.c" @@ -0,0 +1,101 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include + +#include "chipmunk/chipmunk_private.h" + + +cpArray * +cpArrayNew(int size) +{ + cpArray *arr = (cpArray *)cpcalloc(1, sizeof(cpArray)); + + arr->num = 0; + arr->max = (size ? size : 4); + arr->arr = (void **)cpcalloc(arr->max, sizeof(void*)); + + return arr; +} + +void +cpArrayFree(cpArray *arr) +{ + if(arr){ + cpfree(arr->arr); + arr->arr = NULL; + + cpfree(arr); + } +} + +void +cpArrayPush(cpArray *arr, void *object) +{ + if(arr->num == arr->max){ + arr->max = 3*(arr->max + 1)/2; + arr->arr = (void **)cprealloc(arr->arr, arr->max*sizeof(void*)); + } + + arr->arr[arr->num] = object; + arr->num++; +} + +void * +cpArrayPop(cpArray *arr) +{ + arr->num--; + + void *value = arr->arr[arr->num]; + arr->arr[arr->num] = NULL; + + return value; +} + +void +cpArrayDeleteObj(cpArray *arr, void *obj) +{ + for(int i=0; inum; i++){ + if(arr->arr[i] == obj){ + arr->num--; + + arr->arr[i] = arr->arr[arr->num]; + arr->arr[arr->num] = NULL; + + return; + } + } +} + +void +cpArrayFreeEach(cpArray *arr, void (freeFunc)(void*)) +{ + for(int i=0; inum; i++) freeFunc(arr->arr[i]); +} + +cpBool +cpArrayContains(cpArray *arr, void *ptr) +{ + for(int i=0; inum; i++) + if(arr->arr[i] == ptr) return cpTrue; + + return cpFalse; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBBTree.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBBTree.c" new file mode 100644 index 0000000..eb2944b --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBBTree.c" @@ -0,0 +1,896 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "stdlib.h" +#include "stdio.h" + +#include "chipmunk/chipmunk_private.h" + +static inline cpSpatialIndexClass *Klass(); + +typedef struct Node Node; +typedef struct Pair Pair; + +struct cpBBTree { + cpSpatialIndex spatialIndex; + cpBBTreeVelocityFunc velocityFunc; + + cpHashSet *leaves; + Node *root; + + Node *pooledNodes; + Pair *pooledPairs; + cpArray *allocatedBuffers; + + cpTimestamp stamp; +}; + +struct Node { + void *obj; + cpBB bb; + Node *parent; + + union { + // Internal nodes + struct { Node *a, *b; } children; + + // Leaves + struct { + cpTimestamp stamp; + Pair *pairs; + } leaf; + } node; +}; + +// Can't use anonymous unions and still get good x-compiler compatability +#define A node.children.a +#define B node.children.b +#define STAMP node.leaf.stamp +#define PAIRS node.leaf.pairs + +typedef struct Thread { + Pair *prev; + Node *leaf; + Pair *next; +} Thread; + +struct Pair { + Thread a, b; + cpCollisionID id; +}; + +//MARK: Misc Functions + +static inline cpBB +GetBB(cpBBTree *tree, void *obj) +{ + cpBB bb = tree->spatialIndex.bbfunc(obj); + + cpBBTreeVelocityFunc velocityFunc = tree->velocityFunc; + if(velocityFunc){ + cpFloat coef = 0.1f; + cpFloat x = (bb.r - bb.l)*coef; + cpFloat y = (bb.t - bb.b)*coef; + + cpVect v = cpvmult(velocityFunc(obj), 0.1f); + return cpBBNew(bb.l + cpfmin(-x, v.x), bb.b + cpfmin(-y, v.y), bb.r + cpfmax(x, v.x), bb.t + cpfmax(y, v.y)); + } else { + return bb; + } +} + +static inline cpBBTree * +GetTree(cpSpatialIndex *index) +{ + return (index && index->klass == Klass() ? (cpBBTree *)index : NULL); +} + +static inline Node * +GetRootIfTree(cpSpatialIndex *index){ + return (index && index->klass == Klass() ? ((cpBBTree *)index)->root : NULL); +} + +static inline cpBBTree * +GetMasterTree(cpBBTree *tree) +{ + cpBBTree *dynamicTree = GetTree(tree->spatialIndex.dynamicIndex); + return (dynamicTree ? dynamicTree : tree); +} + +static inline void +IncrementStamp(cpBBTree *tree) +{ + cpBBTree *dynamicTree = GetTree(tree->spatialIndex.dynamicIndex); + if(dynamicTree){ + dynamicTree->stamp++; + } else { + tree->stamp++; + } +} + +//MARK: Pair/Thread Functions + +static void +PairRecycle(cpBBTree *tree, Pair *pair) +{ + // Share the pool of the master tree. + // TODO: would be lovely to move the pairs stuff into an external data structure. + tree = GetMasterTree(tree); + + pair->a.next = tree->pooledPairs; + tree->pooledPairs = pair; +} + +static Pair * +PairFromPool(cpBBTree *tree) +{ + // Share the pool of the master tree. + // TODO: would be lovely to move the pairs stuff into an external data structure. + tree = GetMasterTree(tree); + + Pair *pair = tree->pooledPairs; + + if(pair){ + tree->pooledPairs = pair->a.next; + return pair; + } else { + // Pool is exhausted, make more + int count = CP_BUFFER_BYTES/sizeof(Pair); + cpAssertHard(count, "Internal Error: Buffer size is too small."); + + Pair *buffer = (Pair *)cpcalloc(1, CP_BUFFER_BYTES); + cpArrayPush(tree->allocatedBuffers, buffer); + + // push all but the first one, return the first instead + for(int i=1; ia.leaf == thread.leaf) next->a.prev = prev; else next->b.prev = prev; + } + + if(prev){ + if(prev->a.leaf == thread.leaf) prev->a.next = next; else prev->b.next = next; + } else { + thread.leaf->PAIRS = next; + } +} + +static void +PairsClear(Node *leaf, cpBBTree *tree) +{ + Pair *pair = leaf->PAIRS; + leaf->PAIRS = NULL; + + while(pair){ + if(pair->a.leaf == leaf){ + Pair *next = pair->a.next; + ThreadUnlink(pair->b); + PairRecycle(tree, pair); + pair = next; + } else { + Pair *next = pair->b.next; + ThreadUnlink(pair->a); + PairRecycle(tree, pair); + pair = next; + } + } +} + +static void +PairInsert(Node *a, Node *b, cpBBTree *tree) +{ + Pair *nextA = a->PAIRS, *nextB = b->PAIRS; + Pair *pair = PairFromPool(tree); + Pair temp = {{NULL, a, nextA},{NULL, b, nextB}, 0}; + + a->PAIRS = b->PAIRS = pair; + *pair = temp; + + if(nextA){ + if(nextA->a.leaf == a) nextA->a.prev = pair; else nextA->b.prev = pair; + } + + if(nextB){ + if(nextB->a.leaf == b) nextB->a.prev = pair; else nextB->b.prev = pair; + } +} + + +//MARK: Node Functions + +static void +NodeRecycle(cpBBTree *tree, Node *node) +{ + node->parent = tree->pooledNodes; + tree->pooledNodes = node; +} + +static Node * +NodeFromPool(cpBBTree *tree) +{ + Node *node = tree->pooledNodes; + + if(node){ + tree->pooledNodes = node->parent; + return node; + } else { + // Pool is exhausted, make more + int count = CP_BUFFER_BYTES/sizeof(Node); + cpAssertHard(count, "Internal Error: Buffer size is too small."); + + Node *buffer = (Node *)cpcalloc(1, CP_BUFFER_BYTES); + cpArrayPush(tree->allocatedBuffers, buffer); + + // push all but the first one, return the first instead + for(int i=1; iA = value; + value->parent = node; +} + +static inline void +NodeSetB(Node *node, Node *value) +{ + node->B = value; + value->parent = node; +} + +static Node * +NodeNew(cpBBTree *tree, Node *a, Node *b) +{ + Node *node = NodeFromPool(tree); + + node->obj = NULL; + node->bb = cpBBMerge(a->bb, b->bb); + node->parent = NULL; + + NodeSetA(node, a); + NodeSetB(node, b); + + return node; +} + +static inline cpBool +NodeIsLeaf(Node *node) +{ + return (node->obj != NULL); +} + +static inline Node * +NodeOther(Node *node, Node *child) +{ + return (node->A == child ? node->B : node->A); +} + +static inline void +NodeReplaceChild(Node *parent, Node *child, Node *value, cpBBTree *tree) +{ + cpAssertSoft(!NodeIsLeaf(parent), "Internal Error: Cannot replace child of a leaf."); + cpAssertSoft(child == parent->A || child == parent->B, "Internal Error: Node is not a child of parent."); + + if(parent->A == child){ + NodeRecycle(tree, parent->A); + NodeSetA(parent, value); + } else { + NodeRecycle(tree, parent->B); + NodeSetB(parent, value); + } + + for(Node *node=parent; node; node = node->parent){ + node->bb = cpBBMerge(node->A->bb, node->B->bb); + } +} + +//MARK: Subtree Functions + +static inline cpFloat +cpBBProximity(cpBB a, cpBB b) +{ + return cpfabs(a.l + a.r - b.l - b.r) + cpfabs(a.b + a.t - b.b - b.t); +} + +static Node * +SubtreeInsert(Node *subtree, Node *leaf, cpBBTree *tree) +{ + if(subtree == NULL){ + return leaf; + } else if(NodeIsLeaf(subtree)){ + return NodeNew(tree, leaf, subtree); + } else { + cpFloat cost_a = cpBBArea(subtree->B->bb) + cpBBMergedArea(subtree->A->bb, leaf->bb); + cpFloat cost_b = cpBBArea(subtree->A->bb) + cpBBMergedArea(subtree->B->bb, leaf->bb); + + if(cost_a == cost_b){ + cost_a = cpBBProximity(subtree->A->bb, leaf->bb); + cost_b = cpBBProximity(subtree->B->bb, leaf->bb); + } + + if(cost_b < cost_a){ + NodeSetB(subtree, SubtreeInsert(subtree->B, leaf, tree)); + } else { + NodeSetA(subtree, SubtreeInsert(subtree->A, leaf, tree)); + } + + subtree->bb = cpBBMerge(subtree->bb, leaf->bb); + return subtree; + } +} + +static void +SubtreeQuery(Node *subtree, void *obj, cpBB bb, cpSpatialIndexQueryFunc func, void *data) +{ + if(cpBBIntersects(subtree->bb, bb)){ + if(NodeIsLeaf(subtree)){ + func(obj, subtree->obj, 0, data); + } else { + SubtreeQuery(subtree->A, obj, bb, func, data); + SubtreeQuery(subtree->B, obj, bb, func, data); + } + } +} + + +static cpFloat +SubtreeSegmentQuery(Node *subtree, void *obj, cpVect a, cpVect b, cpFloat t_exit, cpSpatialIndexSegmentQueryFunc func, void *data) +{ + if(NodeIsLeaf(subtree)){ + return func(obj, subtree->obj, data); + } else { + cpFloat t_a = cpBBSegmentQuery(subtree->A->bb, a, b); + cpFloat t_b = cpBBSegmentQuery(subtree->B->bb, a, b); + + if(t_a < t_b){ + if(t_a < t_exit) t_exit = cpfmin(t_exit, SubtreeSegmentQuery(subtree->A, obj, a, b, t_exit, func, data)); + if(t_b < t_exit) t_exit = cpfmin(t_exit, SubtreeSegmentQuery(subtree->B, obj, a, b, t_exit, func, data)); + } else { + if(t_b < t_exit) t_exit = cpfmin(t_exit, SubtreeSegmentQuery(subtree->B, obj, a, b, t_exit, func, data)); + if(t_a < t_exit) t_exit = cpfmin(t_exit, SubtreeSegmentQuery(subtree->A, obj, a, b, t_exit, func, data)); + } + + return t_exit; + } +} + +static void +SubtreeRecycle(cpBBTree *tree, Node *node) +{ + if(!NodeIsLeaf(node)){ + SubtreeRecycle(tree, node->A); + SubtreeRecycle(tree, node->B); + NodeRecycle(tree, node); + } +} + +static inline Node * +SubtreeRemove(Node *subtree, Node *leaf, cpBBTree *tree) +{ + if(leaf == subtree){ + return NULL; + } else { + Node *parent = leaf->parent; + if(parent == subtree){ + Node *other = NodeOther(subtree, leaf); + other->parent = subtree->parent; + NodeRecycle(tree, subtree); + return other; + } else { + NodeReplaceChild(parent->parent, parent, NodeOther(parent, leaf), tree); + return subtree; + } + } +} + +//MARK: Marking Functions + +typedef struct MarkContext { + cpBBTree *tree; + Node *staticRoot; + cpSpatialIndexQueryFunc func; + void *data; +} MarkContext; + +static void +MarkLeafQuery(Node *subtree, Node *leaf, cpBool left, MarkContext *context) +{ + if(cpBBIntersects(leaf->bb, subtree->bb)){ + if(NodeIsLeaf(subtree)){ + if(left){ + PairInsert(leaf, subtree, context->tree); + } else { + if(subtree->STAMP < leaf->STAMP) PairInsert(subtree, leaf, context->tree); + context->func(leaf->obj, subtree->obj, 0, context->data); + } + } else { + MarkLeafQuery(subtree->A, leaf, left, context); + MarkLeafQuery(subtree->B, leaf, left, context); + } + } +} + +static void +MarkLeaf(Node *leaf, MarkContext *context) +{ + cpBBTree *tree = context->tree; + if(leaf->STAMP == GetMasterTree(tree)->stamp){ + Node *staticRoot = context->staticRoot; + if(staticRoot) MarkLeafQuery(staticRoot, leaf, cpFalse, context); + + for(Node *node = leaf; node->parent; node = node->parent){ + if(node == node->parent->A){ + MarkLeafQuery(node->parent->B, leaf, cpTrue, context); + } else { + MarkLeafQuery(node->parent->A, leaf, cpFalse, context); + } + } + } else { + Pair *pair = leaf->PAIRS; + while(pair){ + if(leaf == pair->b.leaf){ + pair->id = context->func(pair->a.leaf->obj, leaf->obj, pair->id, context->data); + pair = pair->b.next; + } else { + pair = pair->a.next; + } + } + } +} + +static void +MarkSubtree(Node *subtree, MarkContext *context) +{ + if(NodeIsLeaf(subtree)){ + MarkLeaf(subtree, context); + } else { + MarkSubtree(subtree->A, context); + MarkSubtree(subtree->B, context); // TODO: Force TCO here? + } +} + +//MARK: Leaf Functions + +static Node * +LeafNew(cpBBTree *tree, void *obj, cpBB bb) +{ + Node *node = NodeFromPool(tree); + node->obj = obj; + node->bb = GetBB(tree, obj); + + node->parent = NULL; + node->STAMP = 0; + node->PAIRS = NULL; + + return node; +} + +static cpBool +LeafUpdate(Node *leaf, cpBBTree *tree) +{ + Node *root = tree->root; + cpBB bb = tree->spatialIndex.bbfunc(leaf->obj); + + if(!cpBBContainsBB(leaf->bb, bb)){ + leaf->bb = GetBB(tree, leaf->obj); + + root = SubtreeRemove(root, leaf, tree); + tree->root = SubtreeInsert(root, leaf, tree); + + PairsClear(leaf, tree); + leaf->STAMP = GetMasterTree(tree)->stamp; + + return cpTrue; + } else { + return cpFalse; + } +} + +static cpCollisionID VoidQueryFunc(void *obj1, void *obj2, cpCollisionID id, void *data){return id;} + +static void +LeafAddPairs(Node *leaf, cpBBTree *tree) +{ + cpSpatialIndex *dynamicIndex = tree->spatialIndex.dynamicIndex; + if(dynamicIndex){ + Node *dynamicRoot = GetRootIfTree(dynamicIndex); + if(dynamicRoot){ + cpBBTree *dynamicTree = GetTree(dynamicIndex); + MarkContext context = {dynamicTree, NULL, NULL, NULL}; + MarkLeafQuery(dynamicRoot, leaf, cpTrue, &context); + } + } else { + Node *staticRoot = GetRootIfTree(tree->spatialIndex.staticIndex); + MarkContext context = {tree, staticRoot, VoidQueryFunc, NULL}; + MarkLeaf(leaf, &context); + } +} + +//MARK: Memory Management Functions + +cpBBTree * +cpBBTreeAlloc(void) +{ + return (cpBBTree *)cpcalloc(1, sizeof(cpBBTree)); +} + +static int +leafSetEql(void *obj, Node *node) +{ + return (obj == node->obj); +} + +static void * +leafSetTrans(void *obj, cpBBTree *tree) +{ + return LeafNew(tree, obj, tree->spatialIndex.bbfunc(obj)); +} + +cpSpatialIndex * +cpBBTreeInit(cpBBTree *tree, cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex) +{ + cpSpatialIndexInit((cpSpatialIndex *)tree, Klass(), bbfunc, staticIndex); + + tree->velocityFunc = NULL; + + tree->leaves = cpHashSetNew(0, (cpHashSetEqlFunc)leafSetEql); + tree->root = NULL; + + tree->pooledNodes = NULL; + tree->allocatedBuffers = cpArrayNew(0); + + tree->stamp = 0; + + return (cpSpatialIndex *)tree; +} + +void +cpBBTreeSetVelocityFunc(cpSpatialIndex *index, cpBBTreeVelocityFunc func) +{ + if(index->klass != Klass()){ + cpAssertWarn(cpFalse, "Ignoring cpBBTreeSetVelocityFunc() call to non-tree spatial index."); + return; + } + + ((cpBBTree *)index)->velocityFunc = func; +} + +cpSpatialIndex * +cpBBTreeNew(cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex) +{ + return cpBBTreeInit(cpBBTreeAlloc(), bbfunc, staticIndex); +} + +static void +cpBBTreeDestroy(cpBBTree *tree) +{ + cpHashSetFree(tree->leaves); + + if(tree->allocatedBuffers) cpArrayFreeEach(tree->allocatedBuffers, cpfree); + cpArrayFree(tree->allocatedBuffers); +} + +//MARK: Insert/Remove + +static void +cpBBTreeInsert(cpBBTree *tree, void *obj, cpHashValue hashid) +{ + Node *leaf = (Node *)cpHashSetInsert(tree->leaves, hashid, obj, (cpHashSetTransFunc)leafSetTrans, tree); + + Node *root = tree->root; + tree->root = SubtreeInsert(root, leaf, tree); + + leaf->STAMP = GetMasterTree(tree)->stamp; + LeafAddPairs(leaf, tree); + IncrementStamp(tree); +} + +static void +cpBBTreeRemove(cpBBTree *tree, void *obj, cpHashValue hashid) +{ + Node *leaf = (Node *)cpHashSetRemove(tree->leaves, hashid, obj); + + tree->root = SubtreeRemove(tree->root, leaf, tree); + PairsClear(leaf, tree); + NodeRecycle(tree, leaf); +} + +static cpBool +cpBBTreeContains(cpBBTree *tree, void *obj, cpHashValue hashid) +{ + return (cpHashSetFind(tree->leaves, hashid, obj) != NULL); +} + +//MARK: Reindex + +static void LeafUpdateWrap(Node *leaf, cpBBTree *tree) {LeafUpdate(leaf, tree);} + +static void +cpBBTreeReindexQuery(cpBBTree *tree, cpSpatialIndexQueryFunc func, void *data) +{ + if(!tree->root) return; + + // LeafUpdate() may modify tree->root. Don't cache it. + cpHashSetEach(tree->leaves, (cpHashSetIteratorFunc)LeafUpdateWrap, tree); + + cpSpatialIndex *staticIndex = tree->spatialIndex.staticIndex; + Node *staticRoot = (staticIndex && staticIndex->klass == Klass() ? ((cpBBTree *)staticIndex)->root : NULL); + + MarkContext context = {tree, staticRoot, func, data}; + MarkSubtree(tree->root, &context); + if(staticIndex && !staticRoot) cpSpatialIndexCollideStatic((cpSpatialIndex *)tree, staticIndex, func, data); + + IncrementStamp(tree); +} + +static void +cpBBTreeReindex(cpBBTree *tree) +{ + cpBBTreeReindexQuery(tree, VoidQueryFunc, NULL); +} + +static void +cpBBTreeReindexObject(cpBBTree *tree, void *obj, cpHashValue hashid) +{ + Node *leaf = (Node *)cpHashSetFind(tree->leaves, hashid, obj); + if(leaf){ + if(LeafUpdate(leaf, tree)) LeafAddPairs(leaf, tree); + IncrementStamp(tree); + } +} + +//MARK: Query + +static void +cpBBTreeSegmentQuery(cpBBTree *tree, void *obj, cpVect a, cpVect b, cpFloat t_exit, cpSpatialIndexSegmentQueryFunc func, void *data) +{ + Node *root = tree->root; + if(root) SubtreeSegmentQuery(root, obj, a, b, t_exit, func, data); +} + +static void +cpBBTreeQuery(cpBBTree *tree, void *obj, cpBB bb, cpSpatialIndexQueryFunc func, void *data) +{ + if(tree->root) SubtreeQuery(tree->root, obj, bb, func, data); +} + +//MARK: Misc + +static int +cpBBTreeCount(cpBBTree *tree) +{ + return cpHashSetCount(tree->leaves); +} + +typedef struct eachContext { + cpSpatialIndexIteratorFunc func; + void *data; +} eachContext; + +static void each_helper(Node *node, eachContext *context){context->func(node->obj, context->data);} + +static void +cpBBTreeEach(cpBBTree *tree, cpSpatialIndexIteratorFunc func, void *data) +{ + eachContext context = {func, data}; + cpHashSetEach(tree->leaves, (cpHashSetIteratorFunc)each_helper, &context); +} + +static cpSpatialIndexClass klass = { + (cpSpatialIndexDestroyImpl)cpBBTreeDestroy, + + (cpSpatialIndexCountImpl)cpBBTreeCount, + (cpSpatialIndexEachImpl)cpBBTreeEach, + + (cpSpatialIndexContainsImpl)cpBBTreeContains, + (cpSpatialIndexInsertImpl)cpBBTreeInsert, + (cpSpatialIndexRemoveImpl)cpBBTreeRemove, + + (cpSpatialIndexReindexImpl)cpBBTreeReindex, + (cpSpatialIndexReindexObjectImpl)cpBBTreeReindexObject, + (cpSpatialIndexReindexQueryImpl)cpBBTreeReindexQuery, + + (cpSpatialIndexQueryImpl)cpBBTreeQuery, + (cpSpatialIndexSegmentQueryImpl)cpBBTreeSegmentQuery, +}; + +static inline cpSpatialIndexClass *Klass(){return &klass;} + + +//MARK: Tree Optimization + +static int +cpfcompare(const cpFloat *a, const cpFloat *b){ + return (*a < *b ? -1 : (*b < *a ? 1 : 0)); +} + +static void +fillNodeArray(Node *node, Node ***cursor){ + (**cursor) = node; + (*cursor)++; +} + +static Node * +partitionNodes(cpBBTree *tree, Node **nodes, int count) +{ + if(count == 1){ + return nodes[0]; + } else if(count == 2) { + return NodeNew(tree, nodes[0], nodes[1]); + } + + // Find the AABB for these nodes + cpBB bb = nodes[0]->bb; + for(int i=1; ibb); + + // Split it on it's longest axis + cpBool splitWidth = (bb.r - bb.l > bb.t - bb.b); + + // Sort the bounds and use the median as the splitting point + cpFloat *bounds = (cpFloat *)cpcalloc(count*2, sizeof(cpFloat)); + if(splitWidth){ + for(int i=0; ibb.l; + bounds[2*i + 1] = nodes[i]->bb.r; + } + } else { + for(int i=0; ibb.b; + bounds[2*i + 1] = nodes[i]->bb.t; + } + } + + qsort(bounds, count*2, sizeof(cpFloat), (int (*)(const void *, const void *))cpfcompare); + cpFloat split = (bounds[count - 1] + bounds[count])*0.5f; // use the medain as the split + cpfree(bounds); + + // Generate the child BBs + cpBB a = bb, b = bb; + if(splitWidth) a.r = b.l = split; else a.t = b.b = split; + + // Partition the nodes + int right = count; + for(int left=0; left < right;){ + Node *node = nodes[left]; + if(cpBBMergedArea(node->bb, b) < cpBBMergedArea(node->bb, a)){ +// if(cpBBProximity(node->bb, b) < cpBBProximity(node->bb, a)){ + right--; + nodes[left] = nodes[right]; + nodes[right] = node; + } else { + left++; + } + } + + if(right == count){ + Node *node = NULL; + for(int i=0; iroot; +// Node *node = root; +// int bit = 0; +// unsigned int path = tree->opath; +// +// while(!NodeIsLeaf(node)){ +// node = (path&(1<a : node->b); +// bit = (bit + 1)&(sizeof(unsigned int)*8 - 1); +// } +// +// root = subtreeRemove(root, node, tree); +// tree->root = subtreeInsert(root, node, tree); +// } +//} + +void +cpBBTreeOptimize(cpSpatialIndex *index) +{ + if(index->klass != &klass){ + cpAssertWarn(cpFalse, "Ignoring cpBBTreeOptimize() call to non-tree spatial index."); + return; + } + + cpBBTree *tree = (cpBBTree *)index; + Node *root = tree->root; + if(!root) return; + + int count = cpBBTreeCount(tree); + Node **nodes = (Node **)cpcalloc(count, sizeof(Node *)); + Node **cursor = nodes; + + cpHashSetEach(tree->leaves, (cpHashSetIteratorFunc)fillNodeArray, &cursor); + + SubtreeRecycle(tree, root); + tree->root = partitionNodes(tree, nodes, count); + cpfree(nodes); +} + +//MARK: Debug Draw + +//#define CP_BBTREE_DEBUG_DRAW +#ifdef CP_BBTREE_DEBUG_DRAW +#include "OpenGL/gl.h" +#include "OpenGL/glu.h" +#include + +static void +NodeRender(Node *node, int depth) +{ + if(!NodeIsLeaf(node) && depth <= 10){ + NodeRender(node->a, depth + 1); + NodeRender(node->b, depth + 1); + } + + cpBB bb = node->bb; + +// GLfloat v = depth/2.0f; +// glColor3f(1.0f - v, v, 0.0f); + glLineWidth(cpfmax(5.0f - depth, 1.0f)); + glBegin(GL_LINES); { + glVertex2f(bb.l, bb.b); + glVertex2f(bb.l, bb.t); + + glVertex2f(bb.l, bb.t); + glVertex2f(bb.r, bb.t); + + glVertex2f(bb.r, bb.t); + glVertex2f(bb.r, bb.b); + + glVertex2f(bb.r, bb.b); + glVertex2f(bb.l, bb.b); + }; glEnd(); +} + +void +cpBBTreeRenderDebug(cpSpatialIndex *index){ + if(index->klass != &klass){ + cpAssertWarn(cpFalse, "Ignoring cpBBTreeRenderDebug() call to non-tree spatial index."); + return; + } + + cpBBTree *tree = (cpBBTree *)index; + if(tree->root) NodeRender(tree->root, 0); +} +#endif diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBody.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBody.c" new file mode 100644 index 0000000..a8e0797 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBody.c" @@ -0,0 +1,626 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include +#include + +#include "chipmunk/chipmunk_private.h" + +cpBody* +cpBodyAlloc(void) +{ + return (cpBody *)cpcalloc(1, sizeof(cpBody)); +} + +cpBody * +cpBodyInit(cpBody *body, cpFloat mass, cpFloat moment) +{ + body->space = NULL; + body->shapeList = NULL; + body->arbiterList = NULL; + body->constraintList = NULL; + + body->velocity_func = cpBodyUpdateVelocity; + body->position_func = cpBodyUpdatePosition; + + body->sleeping.root = NULL; + body->sleeping.next = NULL; + body->sleeping.idleTime = 0.0f; + + body->p = cpvzero; + body->v = cpvzero; + body->f = cpvzero; + + body->w = 0.0f; + body->t = 0.0f; + + body->v_bias = cpvzero; + body->w_bias = 0.0f; + + body->userData = NULL; + + // Setters must be called after full initialization so the sanity checks don't assert on garbage data. + cpBodySetMass(body, mass); + cpBodySetMoment(body, moment); + cpBodySetAngle(body, 0.0f); + + return body; +} + +cpBody* +cpBodyNew(cpFloat mass, cpFloat moment) +{ + return cpBodyInit(cpBodyAlloc(), mass, moment); +} + +cpBody* +cpBodyNewKinematic() +{ + cpBody *body = cpBodyNew(0.0f, 0.0f); + cpBodySetType(body, CP_BODY_TYPE_KINEMATIC); + + return body; +} + +cpBody* +cpBodyNewStatic() +{ + cpBody *body = cpBodyNew(0.0f, 0.0f); + cpBodySetType(body, CP_BODY_TYPE_STATIC); + + return body; +} + +void cpBodyDestroy(cpBody *body){} + +void +cpBodyFree(cpBody *body) +{ + if(body){ + cpBodyDestroy(body); + cpfree(body); + } +} + +#ifdef NDEBUG + #define cpAssertSaneBody(body) +#else + static void cpv_assert_nan(cpVect v, char *message){cpAssertHard(v.x == v.x && v.y == v.y, message);} + static void cpv_assert_infinite(cpVect v, char *message){cpAssertHard(cpfabs(v.x) != INFINITY && cpfabs(v.y) != INFINITY, message);} + static void cpv_assert_sane(cpVect v, char *message){cpv_assert_nan(v, message); cpv_assert_infinite(v, message);} + + static void + cpBodySanityCheck(const cpBody *body) + { + cpAssertHard(body->m == body->m && body->m_inv == body->m_inv, "Body's mass is NaN."); + cpAssertHard(body->i == body->i && body->i_inv == body->i_inv, "Body's moment is NaN."); + cpAssertHard(body->m >= 0.0f, "Body's mass is negative."); + cpAssertHard(body->i >= 0.0f, "Body's moment is negative."); + + cpv_assert_sane(body->p, "Body's position is invalid."); + cpv_assert_sane(body->v, "Body's velocity is invalid."); + cpv_assert_sane(body->f, "Body's force is invalid."); + + cpAssertHard(body->a == body->a && cpfabs(body->a) != INFINITY, "Body's angle is invalid."); + cpAssertHard(body->w == body->w && cpfabs(body->w) != INFINITY, "Body's angular velocity is invalid."); + cpAssertHard(body->t == body->t && cpfabs(body->t) != INFINITY, "Body's torque is invalid."); + } + + #define cpAssertSaneBody(body) cpBodySanityCheck(body) +#endif + +cpBool +cpBodyIsSleeping(const cpBody *body) +{ + return (body->sleeping.root != ((cpBody*)0)); +} + +cpBodyType +cpBodyGetType(cpBody *body) +{ + if(body->sleeping.idleTime == INFINITY){ + return CP_BODY_TYPE_STATIC; + } else if(body->m == INFINITY){ + return CP_BODY_TYPE_KINEMATIC; + } else { + return CP_BODY_TYPE_DYNAMIC; + } +} + +void +cpBodySetType(cpBody *body, cpBodyType type) +{ + cpBodyType oldType = cpBodyGetType(body); + if(oldType == type) return; + + // Static bodies have their idle timers set to infinity. + // Non-static bodies should have their idle timer reset. + body->sleeping.idleTime = (type == CP_BODY_TYPE_STATIC ? INFINITY : 0.0f); + + if(type == CP_BODY_TYPE_DYNAMIC){ + body->m = body->i = 0.0f; + body->m_inv = body->i_inv = INFINITY; + + cpBodyAccumulateMassFromShapes(body); + } else { + body->m = body->i = INFINITY; + body->m_inv = body->i_inv = 0.0f; + + body->v = cpvzero; + body->w = 0.0f; + } + + // If the body is added to a space already, we'll need to update some space data structures. + cpSpace *space = cpBodyGetSpace(body); + if(space != NULL){ + cpAssertSpaceUnlocked(space); + + if(oldType == CP_BODY_TYPE_STATIC){ + // TODO This is probably not necessary +// cpBodyActivateStatic(body, NULL); + } else { + cpBodyActivate(body); + } + + // Move the bodies to the correct array. + cpArray *fromArray = cpSpaceArrayForBodyType(space, oldType); + cpArray *toArray = cpSpaceArrayForBodyType(space, type); + if(fromArray != toArray){ + cpArrayDeleteObj(fromArray, body); + cpArrayPush(toArray, body); + } + + // Move the body's shapes to the correct spatial index. + cpSpatialIndex *fromIndex = (oldType == CP_BODY_TYPE_STATIC ? space->staticShapes : space->dynamicShapes); + cpSpatialIndex *toIndex = (type == CP_BODY_TYPE_STATIC ? space->staticShapes : space->dynamicShapes); + if(fromIndex != toIndex){ + CP_BODY_FOREACH_SHAPE(body, shape){ + cpSpatialIndexRemove(fromIndex, shape, shape->hashid); + cpSpatialIndexInsert(toIndex, shape, shape->hashid); + } + } + } +} + + + +// Should *only* be called when shapes with mass info are modified, added or removed. +void +cpBodyAccumulateMassFromShapes(cpBody *body) +{ + if(body == NULL || cpBodyGetType(body) != CP_BODY_TYPE_DYNAMIC) return; + + // Reset the body's mass data. + body->m = body->i = 0.0f; + body->cog = cpvzero; + + // Cache the position to realign it at the end. + cpVect pos = cpBodyGetPosition(body); + + // Accumulate mass from shapes. + CP_BODY_FOREACH_SHAPE(body, shape){ + struct cpShapeMassInfo *info = &shape->massInfo; + cpFloat m = info->m; + + if(m > 0.0f){ + cpFloat msum = body->m + m; + + body->i += m*info->i + cpvdistsq(body->cog, info->cog)*(m*body->m)/msum; + body->cog = cpvlerp(body->cog, info->cog, m/msum); + body->m = msum; + } + } + + // Recalculate the inverses. + body->m_inv = 1.0f/body->m; + body->i_inv = 1.0f/body->i; + + // Realign the body since the CoG has probably moved. + cpBodySetPosition(body, pos); + cpAssertSaneBody(body); +} + +cpSpace * +cpBodyGetSpace(const cpBody *body) +{ + return body->space; +} + +cpFloat +cpBodyGetMass(const cpBody *body) +{ + return body->m; +} + +void +cpBodySetMass(cpBody *body, cpFloat mass) +{ + cpAssertHard(cpBodyGetType(body) == CP_BODY_TYPE_DYNAMIC, "You cannot set the mass of kinematic or static bodies."); + cpAssertHard(0.0f <= mass && mass < INFINITY, "Mass must be positive and finite."); + + cpBodyActivate(body); + body->m = mass; + body->m_inv = 1.0f/mass; + cpAssertSaneBody(body); +} + +cpFloat +cpBodyGetMoment(const cpBody *body) +{ + return body->i; +} + +void +cpBodySetMoment(cpBody *body, cpFloat moment) +{ + cpAssertHard(moment >= 0.0f, "Moment of Inertia must be positive."); + + cpBodyActivate(body); + body->i = moment; + body->i_inv = 1.0f/moment; + cpAssertSaneBody(body); +} + +cpVect +cpBodyGetRotation(const cpBody *body) +{ + return cpv(body->transform.a, body->transform.b); +} + +void +cpBodyAddShape(cpBody *body, cpShape *shape) +{ + cpShape *next = body->shapeList; + if(next) next->prev = shape; + + shape->next = next; + body->shapeList = shape; + + if(shape->massInfo.m > 0.0f){ + cpBodyAccumulateMassFromShapes(body); + } +} + +void +cpBodyRemoveShape(cpBody *body, cpShape *shape) +{ + cpShape *prev = shape->prev; + cpShape *next = shape->next; + + if(prev){ + prev->next = next; + } else { + body->shapeList = next; + } + + if(next){ + next->prev = prev; + } + + shape->prev = NULL; + shape->next = NULL; + + if(cpBodyGetType(body) == CP_BODY_TYPE_DYNAMIC && shape->massInfo.m > 0.0f){ + cpBodyAccumulateMassFromShapes(body); + } +} + +static cpConstraint * +filterConstraints(cpConstraint *node, cpBody *body, cpConstraint *filter) +{ + if(node == filter){ + return cpConstraintNext(node, body); + } else if(node->a == body){ + node->next_a = filterConstraints(node->next_a, body, filter); + } else { + node->next_b = filterConstraints(node->next_b, body, filter); + } + + return node; +} + +void +cpBodyRemoveConstraint(cpBody *body, cpConstraint *constraint) +{ + body->constraintList = filterConstraints(body->constraintList, body, constraint); +} + +// 'p' is the position of the CoG +static void +SetTransform(cpBody *body, cpVect p, cpFloat a) +{ + cpVect rot = cpvforangle(a); + cpVect c = body->cog; + + body->transform = cpTransformNewTranspose( + rot.x, -rot.y, p.x - (c.x*rot.x - c.y*rot.y), + rot.y, rot.x, p.y - (c.x*rot.y + c.y*rot.x) + ); +} + +static inline cpFloat +SetAngle(cpBody *body, cpFloat a) +{ + body->a = a; + cpAssertSaneBody(body); + + return a; +} + +cpVect +cpBodyGetPosition(const cpBody *body) +{ + return cpTransformPoint(body->transform, cpvzero); +} + +void +cpBodySetPosition(cpBody *body, cpVect position) +{ + cpBodyActivate(body); + cpVect p = body->p = cpvadd(cpTransformVect(body->transform, body->cog), position); + cpAssertSaneBody(body); + + SetTransform(body, p, body->a); +} + +cpVect +cpBodyGetCenterOfGravity(const cpBody *body) +{ + return body->cog; +} + +void +cpBodySetCenterOfGravity(cpBody *body, cpVect cog) +{ + cpBodyActivate(body); + body->cog = cog; + cpAssertSaneBody(body); +} + +cpVect +cpBodyGetVelocity(const cpBody *body) +{ + return body->v; +} + +void +cpBodySetVelocity(cpBody *body, cpVect velocity) +{ + cpBodyActivate(body); + body->v = velocity; + cpAssertSaneBody(body); +} + +cpVect +cpBodyGetForce(const cpBody *body) +{ + return body->f; +} + +void +cpBodySetForce(cpBody *body, cpVect force) +{ + cpBodyActivate(body); + body->f = force; + cpAssertSaneBody(body); +} + +cpFloat +cpBodyGetAngle(const cpBody *body) +{ + return body->a; +} + +void +cpBodySetAngle(cpBody *body, cpFloat angle) +{ + cpBodyActivate(body); + SetAngle(body, angle); + + SetTransform(body, body->p, angle); +} + +cpFloat +cpBodyGetAngularVelocity(const cpBody *body) +{ + return body->w; +} + +void +cpBodySetAngularVelocity(cpBody *body, cpFloat angularVelocity) +{ + cpBodyActivate(body); + body->w = angularVelocity; + cpAssertSaneBody(body); +} + +cpFloat +cpBodyGetTorque(const cpBody *body) +{ + return body->t; +} + +void +cpBodySetTorque(cpBody *body, cpFloat torque) +{ + cpBodyActivate(body); + body->t = torque; + cpAssertSaneBody(body); +} + +cpDataPointer +cpBodyGetUserData(const cpBody *body) +{ + return body->userData; +} + +void +cpBodySetUserData(cpBody *body, cpDataPointer userData) +{ + body->userData = userData; +} + +void +cpBodySetVelocityUpdateFunc(cpBody *body, cpBodyVelocityFunc velocityFunc) +{ + body->velocity_func = velocityFunc; +} + +void +cpBodySetPositionUpdateFunc(cpBody *body, cpBodyPositionFunc positionFunc) +{ + body->position_func = positionFunc; +} + +void +cpBodyUpdateVelocity(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt) +{ + // Skip kinematic bodies. + if(cpBodyGetType(body) == CP_BODY_TYPE_KINEMATIC) return; + + cpAssertSoft(body->m > 0.0f && body->i > 0.0f, "Body's mass and moment must be positive to simulate. (Mass: %f Moment: %f)", body->m, body->i); + + body->v = cpvadd(cpvmult(body->v, damping), cpvmult(cpvadd(gravity, cpvmult(body->f, body->m_inv)), dt)); + body->w = body->w*damping + body->t*body->i_inv*dt; + + // Reset forces. + body->f = cpvzero; + body->t = 0.0f; + + cpAssertSaneBody(body); +} + +void +cpBodyUpdatePosition(cpBody *body, cpFloat dt) +{ + cpVect p = body->p = cpvadd(body->p, cpvmult(cpvadd(body->v, body->v_bias), dt)); + cpFloat a = SetAngle(body, body->a + (body->w + body->w_bias)*dt); + SetTransform(body, p, a); + + body->v_bias = cpvzero; + body->w_bias = 0.0f; + + cpAssertSaneBody(body); +} + +cpVect +cpBodyLocalToWorld(const cpBody *body, const cpVect point) +{ + return cpTransformPoint(body->transform, point); +} + +cpVect +cpBodyWorldToLocal(const cpBody *body, const cpVect point) +{ + return cpTransformPoint(cpTransformRigidInverse(body->transform), point); +} + +void +cpBodyApplyForceAtWorldPoint(cpBody *body, cpVect force, cpVect point) +{ + cpBodyActivate(body); + body->f = cpvadd(body->f, force); + + cpVect r = cpvsub(point, cpTransformPoint(body->transform, body->cog)); + body->t += cpvcross(r, force); +} + +void +cpBodyApplyForceAtLocalPoint(cpBody *body, cpVect force, cpVect point) +{ + cpBodyApplyForceAtWorldPoint(body, cpTransformVect(body->transform, force), cpTransformPoint(body->transform, point)); +} + +void +cpBodyApplyImpulseAtWorldPoint(cpBody *body, cpVect impulse, cpVect point) +{ + cpBodyActivate(body); + + cpVect r = cpvsub(point, cpTransformPoint(body->transform, body->cog)); + apply_impulse(body, impulse, r); +} + +void +cpBodyApplyImpulseAtLocalPoint(cpBody *body, cpVect impulse, cpVect point) +{ + cpBodyApplyImpulseAtWorldPoint(body, cpTransformVect(body->transform, impulse), cpTransformPoint(body->transform, point)); +} + +cpVect +cpBodyGetVelocityAtLocalPoint(const cpBody *body, cpVect point) +{ + cpVect r = cpTransformVect(body->transform, cpvsub(point, body->cog)); + return cpvadd(body->v, cpvmult(cpvperp(r), body->w)); +} + +cpVect +cpBodyGetVelocityAtWorldPoint(const cpBody *body, cpVect point) +{ + cpVect r = cpvsub(point, cpTransformPoint(body->transform, body->cog)); + return cpvadd(body->v, cpvmult(cpvperp(r), body->w)); +} + +cpFloat +cpBodyKineticEnergy(const cpBody *body) +{ + // Need to do some fudging to avoid NaNs + cpFloat vsq = cpvdot(body->v, body->v); + cpFloat wsq = body->w*body->w; + return (vsq ? vsq*body->m : 0.0f) + (wsq ? wsq*body->i : 0.0f); +} + +void +cpBodyEachShape(cpBody *body, cpBodyShapeIteratorFunc func, void *data) +{ + cpShape *shape = body->shapeList; + while(shape){ + cpShape *next = shape->next; + func(body, shape, data); + shape = next; + } +} + +void +cpBodyEachConstraint(cpBody *body, cpBodyConstraintIteratorFunc func, void *data) +{ + cpConstraint *constraint = body->constraintList; + while(constraint){ + cpConstraint *next = cpConstraintNext(constraint, body); + func(body, constraint, data); + constraint = next; + } +} + +void +cpBodyEachArbiter(cpBody *body, cpBodyArbiterIteratorFunc func, void *data) +{ + cpArbiter *arb = body->arbiterList; + while(arb){ + cpArbiter *next = cpArbiterNext(arb, body); + + cpBool swapped = arb->swapped; { + arb->swapped = (body == arb->body_b); + func(body, arb, data); + } arb->swapped = swapped; + + arb = next; + } +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpCollision.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpCollision.c" new file mode 100644 index 0000000..0d31473 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpCollision.c" @@ -0,0 +1,726 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include +#include + +#include "chipmunk/chipmunk_private.h" +#include "chipmunk/cpRobust.h" + +#if DEBUG && 0 +#include "ChipmunkDemo.h" +#define DRAW_ALL 0 +#define DRAW_GJK (0 || DRAW_ALL) +#define DRAW_EPA (0 || DRAW_ALL) +#define DRAW_CLOSEST (0 || DRAW_ALL) +#define DRAW_CLIP (0 || DRAW_ALL) + +#define PRINT_LOG 0 +#endif + +#define MAX_GJK_ITERATIONS 30 +#define MAX_EPA_ITERATIONS 30 +#define WARN_GJK_ITERATIONS 20 +#define WARN_EPA_ITERATIONS 20 + +static inline void +cpCollisionInfoPushContact(struct cpCollisionInfo *info, cpVect p1, cpVect p2, cpHashValue hash) +{ + cpAssertSoft(info->count <= CP_MAX_CONTACTS_PER_ARBITER, "Internal error: Tried to push too many contacts."); + + struct cpContact *con = &info->arr[info->count]; + con->r1 = p1; + con->r2 = p2; + con->hash = hash; + + info->count++; +} + +//MARK: Support Points and Edges: + +// Support points are the maximal points on a shape's perimeter along a certain axis. +// The GJK and EPA algorithms use support points to iteratively sample the surface of the two shapes' minkowski difference. + +static inline int +PolySupportPointIndex(const int count, const struct cpSplittingPlane *planes, const cpVect n) +{ + cpFloat max = -INFINITY; + int index = 0; + + for(int i=0; i max){ + max = d; + index = i; + } + } + + return index; +} + +struct SupportPoint { + cpVect p; + // Save an index of the point so it can be cheaply looked up as a starting point for the next frame. + cpCollisionID index; +}; + +static inline struct SupportPoint +SupportPointNew(cpVect p, cpCollisionID index) +{ + struct SupportPoint point = {p, index}; + return point; +} + +typedef struct SupportPoint (*SupportPointFunc)(const cpShape *shape, const cpVect n); + +static inline struct SupportPoint +CircleSupportPoint(const cpCircleShape *circle, const cpVect n) +{ + return SupportPointNew(circle->tc, 0); +} + +static inline struct SupportPoint +SegmentSupportPoint(const cpSegmentShape *seg, const cpVect n) +{ + if(cpvdot(seg->ta, n) > cpvdot(seg->tb, n)){ + return SupportPointNew(seg->ta, 0); + } else { + return SupportPointNew(seg->tb, 1); + } +} + +static inline struct SupportPoint +PolySupportPoint(const cpPolyShape *poly, const cpVect n) +{ + const struct cpSplittingPlane *planes = poly->planes; + int i = PolySupportPointIndex(poly->count, planes, n); + return SupportPointNew(planes[i].v0, i); +} + +// A point on the surface of two shape's minkowski difference. +struct MinkowskiPoint { + // Cache the two original support points. + cpVect a, b; + // b - a + cpVect ab; + // Concatenate the two support point indexes. + cpCollisionID id; +}; + +static inline struct MinkowskiPoint +MinkowskiPointNew(const struct SupportPoint a, const struct SupportPoint b) +{ + struct MinkowskiPoint point = {a.p, b.p, cpvsub(b.p, a.p), (a.index & 0xFF)<<8 | (b.index & 0xFF)}; + return point; +} + +struct SupportContext { + const cpShape *shape1, *shape2; + SupportPointFunc func1, func2; +}; + +// Calculate the maximal point on the minkowski difference of two shapes along a particular axis. +static inline struct MinkowskiPoint +Support(const struct SupportContext *ctx, const cpVect n) +{ + struct SupportPoint a = ctx->func1(ctx->shape1, cpvneg(n)); + struct SupportPoint b = ctx->func2(ctx->shape2, n); + return MinkowskiPointNew(a, b); +} + +struct EdgePoint { + cpVect p; + // Keep a hash value for Chipmunk's collision hashing mechanism. + cpHashValue hash; +}; + +// Support edges are the edges of a polygon or segment shape that are in contact. +struct Edge { + struct EdgePoint a, b; + cpFloat r; + cpVect n; +}; + +static struct Edge +SupportEdgeForPoly(const cpPolyShape *poly, const cpVect n) +{ + int count = poly->count; + int i1 = PolySupportPointIndex(poly->count, poly->planes, n); + + // TODO: get rid of mod eventually, very expensive on ARM + int i0 = (i1 - 1 + count)%count; + int i2 = (i1 + 1)%count; + + const struct cpSplittingPlane *planes = poly->planes; + cpHashValue hashid = poly->shape.hashid; + if(cpvdot(n, planes[i1].n) > cpvdot(n, planes[i2].n)){ + struct Edge edge = {{planes[i0].v0, CP_HASH_PAIR(hashid, i0)}, {planes[i1].v0, CP_HASH_PAIR(hashid, i1)}, poly->r, planes[i1].n}; + return edge; + } else { + struct Edge edge = {{planes[i1].v0, CP_HASH_PAIR(hashid, i1)}, {planes[i2].v0, CP_HASH_PAIR(hashid, i2)}, poly->r, planes[i2].n}; + return edge; + } +} + +static struct Edge +SupportEdgeForSegment(const cpSegmentShape *seg, const cpVect n) +{ + cpHashValue hashid = seg->shape.hashid; + if(cpvdot(seg->tn, n) > 0.0){ + struct Edge edge = {{seg->ta, CP_HASH_PAIR(hashid, 0)}, {seg->tb, CP_HASH_PAIR(hashid, 1)}, seg->r, seg->tn}; + return edge; + } else { + struct Edge edge = {{seg->tb, CP_HASH_PAIR(hashid, 1)}, {seg->ta, CP_HASH_PAIR(hashid, 0)}, seg->r, cpvneg(seg->tn)}; + return edge; + } +} + +// Find the closest p(t) to (0, 0) where p(t) = a*(1-t)/2 + b*(1+t)/2 +// The range for t is [-1, 1] to avoid floating point issues if the parameters are swapped. +static inline cpFloat +ClosestT(const cpVect a, const cpVect b) +{ + cpVect delta = cpvsub(b, a); + return -cpfclamp(cpvdot(delta, cpvadd(a, b))/cpvlengthsq(delta), -1.0f, 1.0f); +} + +// Basically the same as cpvlerp(), except t = [-1, 1] +static inline cpVect +LerpT(const cpVect a, const cpVect b, const cpFloat t) +{ + cpFloat ht = 0.5f*t; + return cpvadd(cpvmult(a, 0.5f - ht), cpvmult(b, 0.5f + ht)); +} + +// Closest points on the surface of two shapes. +struct ClosestPoints { + // Surface points in absolute coordinates. + cpVect a, b; + // Minimum separating axis of the two shapes. + cpVect n; + // Signed distance between the points. + cpFloat d; + // Concatenation of the id's of the minkoski points. + cpCollisionID id; +}; + +// Calculate the closest points on two shapes given the closest edge on their minkowski difference to (0, 0) +static inline struct ClosestPoints +ClosestPointsNew(const struct MinkowskiPoint v0, const struct MinkowskiPoint v1) +{ + // Find the closest p(t) on the minkowski difference to (0, 0) + cpFloat t = ClosestT(v0.ab, v1.ab); + cpVect p = LerpT(v0.ab, v1.ab, t); + + // Interpolate the original support points using the same 't' value as above. + // This gives you the closest surface points in absolute coordinates. NEAT! + cpVect pa = LerpT(v0.a, v1.a, t); + cpVect pb = LerpT(v0.b, v1.b, t); + cpCollisionID id = (v0.id & 0xFFFF)<<16 | (v1.id & 0xFFFF); + + // First try calculating the MSA from the minkowski difference edge. + // This gives us a nice, accurate MSA when the surfaces are close together. + cpVect delta = cpvsub(v1.ab, v0.ab); + cpVect n = cpvnormalize(cpvrperp(delta)); + cpFloat d = cpvdot(n, p); + + if(d <= 0.0f || (-1.0f < t && t < 1.0f)){ + // If the shapes are overlapping, or we have a regular vertex/edge collision, we are done. + struct ClosestPoints points = {pa, pb, n, d, id}; + return points; + } else { + // Vertex/vertex collisions need special treatment since the MSA won't be shared with an axis of the minkowski difference. + cpFloat d2 = cpvlength(p); + cpVect n2 = cpvmult(p, 1.0f/(d2 + CPFLOAT_MIN)); + + struct ClosestPoints points = {pa, pb, n2, d2, id}; + return points; + } +} + +//MARK: EPA Functions + +static inline cpFloat +ClosestDist(const cpVect v0,const cpVect v1) +{ + return cpvlengthsq(LerpT(v0, v1, ClosestT(v0, v1))); +} + +// Recursive implementation of the EPA loop. +// Each recursion adds a point to the convex hull until it's known that we have the closest point on the surface. +static struct ClosestPoints +EPARecurse(const struct SupportContext *ctx, const int count, const struct MinkowskiPoint *hull, const int iteration) +{ + int mini = 0; + cpFloat minDist = INFINITY; + + // TODO: precalculate this when building the hull and save a step. + // Find the closest segment hull[i] and hull[i + 1] to (0, 0) + for(int j=0, i=count-1; j MAX_GJK_ITERATIONS){ + cpAssertWarn(iteration < WARN_GJK_ITERATIONS, "High GJK iterations: %d", iteration); + return ClosestPointsNew(v0, v1); + } + + if(cpCheckPointGreater(v1.ab, v0.ab, cpvzero)){ + // Origin is behind axis. Flip and try again. + return GJKRecurse(ctx, v1, v0, iteration); + } else { + cpFloat t = ClosestT(v0.ab, v1.ab); + cpVect n = (-1.0f < t && t < 1.0f ? cpvperp(cpvsub(v1.ab, v0.ab)) : cpvneg(LerpT(v0.ab, v1.ab, t))); + struct MinkowskiPoint p = Support(ctx, n); + +#if DRAW_GJK + ChipmunkDebugDrawSegment(v0.ab, v1.ab, RGBAColor(1, 1, 1, 1)); + cpVect c = cpvlerp(v0.ab, v1.ab, 0.5); + ChipmunkDebugDrawSegment(c, cpvadd(c, cpvmult(cpvnormalize(n), 5.0)), RGBAColor(1, 0, 0, 1)); + + ChipmunkDebugDrawDot(5.0, p.ab, LAColor(1, 1)); +#endif + + if(cpCheckPointGreater(p.ab, v0.ab, cpvzero) && cpCheckPointGreater(v1.ab, p.ab, cpvzero)){ + // The triangle v0, p, v1 contains the origin. Use EPA to find the MSA. + cpAssertWarn(iteration < WARN_GJK_ITERATIONS, "High GJK->EPA iterations: %d", iteration); + return EPA(ctx, v0, p, v1); + } else { + if(cpCheckAxis(v0.ab, v1.ab, p.ab, n)){ + // The edge v0, v1 that we already have is the closest to (0, 0) since p was not closer. + cpAssertWarn(iteration < WARN_GJK_ITERATIONS, "High GJK iterations: %d", iteration); + return ClosestPointsNew(v0, v1); + } else { + // p was closer to the origin than our existing edge. + // Need to figure out which existing point to drop. + if(ClosestDist(v0.ab, p.ab) < ClosestDist(p.ab, v1.ab)){ + return GJKRecurse(ctx, v0, p, iteration + 1); + } else { + return GJKRecurse(ctx, p, v1, iteration + 1); + } + } + } + } +} + +// Get a SupportPoint from a cached shape and index. +static struct SupportPoint +ShapePoint(const cpShape *shape, const int i) +{ + switch(shape->klass->type){ + case CP_CIRCLE_SHAPE: { + return SupportPointNew(((cpCircleShape *)shape)->tc, 0); + } case CP_SEGMENT_SHAPE: { + cpSegmentShape *seg = (cpSegmentShape *)shape; + return SupportPointNew(i == 0 ? seg->ta : seg->tb, i); + } case CP_POLY_SHAPE: { + cpPolyShape *poly = (cpPolyShape *)shape; + // Poly shapes may change vertex count. + int index = (i < poly->count ? i : 0); + return SupportPointNew(poly->planes[index].v0, index); + } default: { + return SupportPointNew(cpvzero, 0); + } + } +} + +// Find the closest points between two shapes using the GJK algorithm. +static struct ClosestPoints +GJK(const struct SupportContext *ctx, cpCollisionID *id) +{ +#if DRAW_GJK || DRAW_EPA + int count1 = 1; + int count2 = 1; + + switch(ctx->shape1->klass->type){ + case CP_SEGMENT_SHAPE: count1 = 2; break; + case CP_POLY_SHAPE: count1 = ((cpPolyShape *)ctx->shape1)->count; break; + default: break; + } + + switch(ctx->shape2->klass->type){ + case CP_SEGMENT_SHAPE: count1 = 2; break; + case CP_POLY_SHAPE: count2 = ((cpPolyShape *)ctx->shape2)->count; break; + default: break; + } + + + // draw the minkowski difference origin + cpVect origin = cpvzero; + ChipmunkDebugDrawDot(5.0, origin, RGBAColor(1,0,0,1)); + + int mdiffCount = count1*count2; + cpVect *mdiffVerts = alloca(mdiffCount*sizeof(cpVect)); + + for(int i=0; ishape2, j).p, ShapePoint(ctx->shape1, i).p); + mdiffVerts[i*count2 + j] = v; + ChipmunkDebugDrawDot(2.0, v, RGBAColor(1, 0, 0, 1)); + } + } + + cpVect *hullVerts = alloca(mdiffCount*sizeof(cpVect)); + int hullCount = cpConvexHull(mdiffCount, mdiffVerts, hullVerts, NULL, 0.0); + + ChipmunkDebugDrawPolygon(hullCount, hullVerts, 0.0, RGBAColor(1, 0, 0, 1), RGBAColor(1, 0, 0, 0.25)); +#endif + + struct MinkowskiPoint v0, v1; + if(*id){ + // Use the minkowski points from the last frame as a starting point using the cached indexes. + v0 = MinkowskiPointNew(ShapePoint(ctx->shape1, (*id>>24)&0xFF), ShapePoint(ctx->shape2, (*id>>16)&0xFF)); + v1 = MinkowskiPointNew(ShapePoint(ctx->shape1, (*id>> 8)&0xFF), ShapePoint(ctx->shape2, (*id )&0xFF)); + } else { + // No cached indexes, use the shapes' bounding box centers as a guess for a starting axis. + cpVect axis = cpvperp(cpvsub(cpBBCenter(ctx->shape1->bb), cpBBCenter(ctx->shape2->bb))); + v0 = Support(ctx, axis); + v1 = Support(ctx, cpvneg(axis)); + } + + struct ClosestPoints points = GJKRecurse(ctx, v0, v1, 1); + *id = points.id; + return points; +} + +//MARK: Contact Clipping + +// Given two support edges, find contact point pairs on their surfaces. +static inline void +ContactPoints(const struct Edge e1, const struct Edge e2, const struct ClosestPoints points, struct cpCollisionInfo *info) +{ + cpFloat mindist = e1.r + e2.r; + if(points.d <= mindist){ +#ifdef DRAW_CLIP + ChipmunkDebugDrawFatSegment(e1.a.p, e1.b.p, e1.r, RGBAColor(0, 1, 0, 1), LAColor(0, 0)); + ChipmunkDebugDrawFatSegment(e2.a.p, e2.b.p, e2.r, RGBAColor(1, 0, 0, 1), LAColor(0, 0)); +#endif + cpVect n = info->n = points.n; + + // Distances along the axis parallel to n + cpFloat d_e1_a = cpvcross(e1.a.p, n); + cpFloat d_e1_b = cpvcross(e1.b.p, n); + cpFloat d_e2_a = cpvcross(e2.a.p, n); + cpFloat d_e2_b = cpvcross(e2.b.p, n); + + // TODO + min isn't a complete fix. + cpFloat e1_denom = 1.0f/(d_e1_b - d_e1_a + CPFLOAT_MIN); + cpFloat e2_denom = 1.0f/(d_e2_b - d_e2_a + CPFLOAT_MIN); + + // Project the endpoints of the two edges onto the opposing edge, clamping them as necessary. + // Compare the projected points to the collision normal to see if the shapes overlap there. + { + cpVect p1 = cpvadd(cpvmult(n, e1.r), cpvlerp(e1.a.p, e1.b.p, cpfclamp01((d_e2_b - d_e1_a)*e1_denom))); + cpVect p2 = cpvadd(cpvmult(n, -e2.r), cpvlerp(e2.a.p, e2.b.p, cpfclamp01((d_e1_a - d_e2_a)*e2_denom))); + cpFloat dist = cpvdot(cpvsub(p2, p1), n); + if(dist <= 0.0f){ + cpHashValue hash_1a2b = CP_HASH_PAIR(e1.a.hash, e2.b.hash); + cpCollisionInfoPushContact(info, p1, p2, hash_1a2b); + } + }{ + cpVect p1 = cpvadd(cpvmult(n, e1.r), cpvlerp(e1.a.p, e1.b.p, cpfclamp01((d_e2_a - d_e1_a)*e1_denom))); + cpVect p2 = cpvadd(cpvmult(n, -e2.r), cpvlerp(e2.a.p, e2.b.p, cpfclamp01((d_e1_b - d_e2_a)*e2_denom))); + cpFloat dist = cpvdot(cpvsub(p2, p1), n); + if(dist <= 0.0f){ + cpHashValue hash_1b2a = CP_HASH_PAIR(e1.b.hash, e2.a.hash); + cpCollisionInfoPushContact(info, p1, p2, hash_1b2a); + } + } + } +} + +//MARK: Collision Functions + +typedef void (*CollisionFunc)(const cpShape *a, const cpShape *b, struct cpCollisionInfo *info); + +// Collide circle shapes. +static void +CircleToCircle(const cpCircleShape *c1, const cpCircleShape *c2, struct cpCollisionInfo *info) +{ + cpFloat mindist = c1->r + c2->r; + cpVect delta = cpvsub(c2->tc, c1->tc); + cpFloat distsq = cpvlengthsq(delta); + + if(distsq < mindist*mindist){ + cpFloat dist = cpfsqrt(distsq); + cpVect n = info->n = (dist ? cpvmult(delta, 1.0f/dist) : cpv(1.0f, 0.0f)); + cpCollisionInfoPushContact(info, cpvadd(c1->tc, cpvmult(n, c1->r)), cpvadd(c2->tc, cpvmult(n, -c2->r)), 0); + } +} + +static void +CircleToSegment(const cpCircleShape *circle, const cpSegmentShape *segment, struct cpCollisionInfo *info) +{ + cpVect seg_a = segment->ta; + cpVect seg_b = segment->tb; + cpVect center = circle->tc; + + // Find the closest point on the segment to the circle. + cpVect seg_delta = cpvsub(seg_b, seg_a); + cpFloat closest_t = cpfclamp01(cpvdot(seg_delta, cpvsub(center, seg_a))/cpvlengthsq(seg_delta)); + cpVect closest = cpvadd(seg_a, cpvmult(seg_delta, closest_t)); + + // Compare the radii of the two shapes to see if they are colliding. + cpFloat mindist = circle->r + segment->r; + cpVect delta = cpvsub(closest, center); + cpFloat distsq = cpvlengthsq(delta); + if(distsq < mindist*mindist){ + cpFloat dist = cpfsqrt(distsq); + // Handle coincident shapes as gracefully as possible. + cpVect n = info->n = (dist ? cpvmult(delta, 1.0f/dist) : segment->tn); + + // Reject endcap collisions if tangents are provided. + cpVect rot = cpBodyGetRotation(segment->shape.body); + if( + (closest_t != 0.0f || cpvdot(n, cpvrotate(segment->a_tangent, rot)) >= 0.0) && + (closest_t != 1.0f || cpvdot(n, cpvrotate(segment->b_tangent, rot)) >= 0.0) + ){ + cpCollisionInfoPushContact(info, cpvadd(center, cpvmult(n, circle->r)), cpvadd(closest, cpvmult(n, -segment->r)), 0); + } + } +} + +static void +SegmentToSegment(const cpSegmentShape *seg1, const cpSegmentShape *seg2, struct cpCollisionInfo *info) +{ + struct SupportContext context = {(cpShape *)seg1, (cpShape *)seg2, (SupportPointFunc)SegmentSupportPoint, (SupportPointFunc)SegmentSupportPoint}; + struct ClosestPoints points = GJK(&context, &info->id); + +#if DRAW_CLOSEST +#if PRINT_LOG +// ChipmunkDemoPrintString("Distance: %.2f\n", points.d); +#endif + + ChipmunkDebugDrawDot(6.0, points.a, RGBAColor(1, 1, 1, 1)); + ChipmunkDebugDrawDot(6.0, points.b, RGBAColor(1, 1, 1, 1)); + ChipmunkDebugDrawSegment(points.a, points.b, RGBAColor(1, 1, 1, 1)); + ChipmunkDebugDrawSegment(points.a, cpvadd(points.a, cpvmult(points.n, 10.0)), RGBAColor(1, 0, 0, 1)); +#endif + + cpVect n = points.n; + cpVect rot1 = cpBodyGetRotation(seg1->shape.body); + cpVect rot2 = cpBodyGetRotation(seg2->shape.body); + + // If the closest points are nearer than the sum of the radii... + if( + points.d <= (seg1->r + seg2->r) && ( + // Reject endcap collisions if tangents are provided. + (!cpveql(points.a, seg1->ta) || cpvdot(n, cpvrotate(seg1->a_tangent, rot1)) <= 0.0) && + (!cpveql(points.a, seg1->tb) || cpvdot(n, cpvrotate(seg1->b_tangent, rot1)) <= 0.0) && + (!cpveql(points.b, seg2->ta) || cpvdot(n, cpvrotate(seg2->a_tangent, rot2)) >= 0.0) && + (!cpveql(points.b, seg2->tb) || cpvdot(n, cpvrotate(seg2->b_tangent, rot2)) >= 0.0) + ) + ){ + ContactPoints(SupportEdgeForSegment(seg1, n), SupportEdgeForSegment(seg2, cpvneg(n)), points, info); + } +} + +static void +PolyToPoly(const cpPolyShape *poly1, const cpPolyShape *poly2, struct cpCollisionInfo *info) +{ + struct SupportContext context = {(cpShape *)poly1, (cpShape *)poly2, (SupportPointFunc)PolySupportPoint, (SupportPointFunc)PolySupportPoint}; + struct ClosestPoints points = GJK(&context, &info->id); + +#if DRAW_CLOSEST +#if PRINT_LOG +// ChipmunkDemoPrintString("Distance: %.2f\n", points.d); +#endif + + ChipmunkDebugDrawDot(3.0, points.a, RGBAColor(1, 1, 1, 1)); + ChipmunkDebugDrawDot(3.0, points.b, RGBAColor(1, 1, 1, 1)); + ChipmunkDebugDrawSegment(points.a, points.b, RGBAColor(1, 1, 1, 1)); + ChipmunkDebugDrawSegment(points.a, cpvadd(points.a, cpvmult(points.n, 10.0)), RGBAColor(1, 0, 0, 1)); +#endif + + // If the closest points are nearer than the sum of the radii... + if(points.d - poly1->r - poly2->r <= 0.0){ + ContactPoints(SupportEdgeForPoly(poly1, points.n), SupportEdgeForPoly(poly2, cpvneg(points.n)), points, info); + } +} + +static void +SegmentToPoly(const cpSegmentShape *seg, const cpPolyShape *poly, struct cpCollisionInfo *info) +{ + struct SupportContext context = {(cpShape *)seg, (cpShape *)poly, (SupportPointFunc)SegmentSupportPoint, (SupportPointFunc)PolySupportPoint}; + struct ClosestPoints points = GJK(&context, &info->id); + +#if DRAW_CLOSEST +#if PRINT_LOG +// ChipmunkDemoPrintString("Distance: %.2f\n", points.d); +#endif + + ChipmunkDebugDrawDot(3.0, points.a, RGBAColor(1, 1, 1, 1)); + ChipmunkDebugDrawDot(3.0, points.b, RGBAColor(1, 1, 1, 1)); + ChipmunkDebugDrawSegment(points.a, points.b, RGBAColor(1, 1, 1, 1)); + ChipmunkDebugDrawSegment(points.a, cpvadd(points.a, cpvmult(points.n, 10.0)), RGBAColor(1, 0, 0, 1)); +#endif + + cpVect n = points.n; + cpVect rot = cpBodyGetRotation(seg->shape.body); + + if( + // If the closest points are nearer than the sum of the radii... + points.d - seg->r - poly->r <= 0.0 && ( + // Reject endcap collisions if tangents are provided. + (!cpveql(points.a, seg->ta) || cpvdot(n, cpvrotate(seg->a_tangent, rot)) <= 0.0) && + (!cpveql(points.a, seg->tb) || cpvdot(n, cpvrotate(seg->b_tangent, rot)) <= 0.0) + ) + ){ + ContactPoints(SupportEdgeForSegment(seg, n), SupportEdgeForPoly(poly, cpvneg(n)), points, info); + } +} + +static void +CircleToPoly(const cpCircleShape *circle, const cpPolyShape *poly, struct cpCollisionInfo *info) +{ + struct SupportContext context = {(cpShape *)circle, (cpShape *)poly, (SupportPointFunc)CircleSupportPoint, (SupportPointFunc)PolySupportPoint}; + struct ClosestPoints points = GJK(&context, &info->id); + +#if DRAW_CLOSEST + ChipmunkDebugDrawDot(3.0, points.a, RGBAColor(1, 1, 1, 1)); + ChipmunkDebugDrawDot(3.0, points.b, RGBAColor(1, 1, 1, 1)); + ChipmunkDebugDrawSegment(points.a, points.b, RGBAColor(1, 1, 1, 1)); + ChipmunkDebugDrawSegment(points.a, cpvadd(points.a, cpvmult(points.n, 10.0)), RGBAColor(1, 0, 0, 1)); +#endif + + // If the closest points are nearer than the sum of the radii... + if(points.d <= circle->r + poly->r){ + cpVect n = info->n = points.n; + cpCollisionInfoPushContact(info, cpvadd(points.a, cpvmult(n, circle->r)), cpvadd(points.b, cpvmult(n, poly->r)), 0); + } +} + +static void +CollisionError(const cpShape *circle, const cpShape *poly, struct cpCollisionInfo *info) +{ + cpAssertHard(cpFalse, "Internal Error: Shape types are not sorted."); +} + + +static const CollisionFunc BuiltinCollisionFuncs[9] = { + (CollisionFunc)CircleToCircle, + CollisionError, + CollisionError, + (CollisionFunc)CircleToSegment, + (CollisionFunc)SegmentToSegment, + CollisionError, + (CollisionFunc)CircleToPoly, + (CollisionFunc)SegmentToPoly, + (CollisionFunc)PolyToPoly, +}; +static const CollisionFunc *CollisionFuncs = BuiltinCollisionFuncs; + +struct cpCollisionInfo +cpCollide(const cpShape *a, const cpShape *b, cpCollisionID id, struct cpContact *contacts) +{ + struct cpCollisionInfo info = {a, b, id, cpvzero, 0, contacts}; + + // Make sure the shape types are in order. + if(a->klass->type > b->klass->type){ + info.a = b; + info.b = a; + } + + CollisionFuncs[info.a->klass->type + info.b->klass->type*CP_NUM_SHAPES](info.a, info.b, &info); + +// if(0){ +// for(int i=0; iklass = klass; + + constraint->a = a; + constraint->b = b; + constraint->space = NULL; + + constraint->next_a = NULL; + constraint->next_b = NULL; + + constraint->maxForce = (cpFloat)INFINITY; + constraint->errorBias = cpfpow(1.0f - 0.1f, 60.0f); + constraint->maxBias = (cpFloat)INFINITY; + + constraint->collideBodies = cpTrue; + + constraint->preSolve = NULL; + constraint->postSolve = NULL; +} + +cpSpace * +cpConstraintGetSpace(const cpConstraint *constraint) +{ + return constraint->space; +} + +cpBody * +cpConstraintGetBodyA(const cpConstraint *constraint) +{ + return constraint->a; +} + +cpBody * +cpConstraintGetBodyB(const cpConstraint *constraint) +{ + return constraint->b; +} + +cpFloat +cpConstraintGetMaxForce(const cpConstraint *constraint) +{ + return constraint->maxForce; +} + +void +cpConstraintSetMaxForce(cpConstraint *constraint, cpFloat maxForce) +{ + cpAssertHard(maxForce >= 0.0f, "maxForce must be positive."); + cpConstraintActivateBodies(constraint); + constraint->maxForce = maxForce; +} + +cpFloat +cpConstraintGetErrorBias(const cpConstraint *constraint) +{ + return constraint->errorBias; +} + +void +cpConstraintSetErrorBias(cpConstraint *constraint, cpFloat errorBias) +{ + cpAssertHard(errorBias >= 0.0f, "errorBias must be positive."); + cpConstraintActivateBodies(constraint); + constraint->errorBias = errorBias; +} + +cpFloat +cpConstraintGetMaxBias(const cpConstraint *constraint) +{ + return constraint->maxBias; +} + +void +cpConstraintSetMaxBias(cpConstraint *constraint, cpFloat maxBias) +{ + cpAssertHard(maxBias >= 0.0f, "maxBias must be positive."); + cpConstraintActivateBodies(constraint); + constraint->maxBias = maxBias; +} + +cpBool +cpConstraintGetCollideBodies(const cpConstraint *constraint) +{ + return constraint->collideBodies; +} + +void +cpConstraintSetCollideBodies(cpConstraint *constraint, cpBool collideBodies) +{ + cpConstraintActivateBodies(constraint); + constraint->collideBodies = collideBodies; +} + +cpConstraintPreSolveFunc +cpConstraintGetPreSolveFunc(const cpConstraint *constraint) +{ + return constraint->preSolve; +} + +void +cpConstraintSetPreSolveFunc(cpConstraint *constraint, cpConstraintPreSolveFunc preSolveFunc) +{ + constraint->preSolve = preSolveFunc; +} + +cpConstraintPostSolveFunc +cpConstraintGetPostSolveFunc(const cpConstraint *constraint) +{ + return constraint->postSolve; +} + +void +cpConstraintSetPostSolveFunc(cpConstraint *constraint, cpConstraintPostSolveFunc postSolveFunc) +{ + constraint->postSolve = postSolveFunc; +} + +cpDataPointer +cpConstraintGetUserData(const cpConstraint *constraint) +{ + return constraint->userData; +} + +void +cpConstraintSetUserData(cpConstraint *constraint, cpDataPointer userData) +{ + constraint->userData = userData; +} + + +cpFloat +cpConstraintGetImpulse(cpConstraint *constraint) +{ + return constraint->klass->getImpulse(constraint); +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedRotarySpring.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedRotarySpring.c" new file mode 100644 index 0000000..8d38a54 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedRotarySpring.c" @@ -0,0 +1,178 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +static cpFloat +defaultSpringTorque(cpDampedRotarySpring *spring, cpFloat relativeAngle){ + return (relativeAngle - spring->restAngle)*spring->stiffness; +} + +static void +preStep(cpDampedRotarySpring *spring, cpFloat dt) +{ + cpBody *a = spring->constraint.a; + cpBody *b = spring->constraint.b; + + cpFloat moment = a->i_inv + b->i_inv; + cpAssertSoft(moment != 0.0, "Unsolvable spring."); + spring->iSum = 1.0f/moment; + + spring->w_coef = 1.0f - cpfexp(-spring->damping*dt*moment); + spring->target_wrn = 0.0f; + + // apply spring torque + cpFloat j_spring = spring->springTorqueFunc((cpConstraint *)spring, a->a - b->a)*dt; + spring->jAcc = j_spring; + + a->w -= j_spring*a->i_inv; + b->w += j_spring*b->i_inv; +} + +static void applyCachedImpulse(cpDampedRotarySpring *spring, cpFloat dt_coef){} + +static void +applyImpulse(cpDampedRotarySpring *spring, cpFloat dt) +{ + cpBody *a = spring->constraint.a; + cpBody *b = spring->constraint.b; + + // compute relative velocity + cpFloat wrn = a->w - b->w;//normal_relative_velocity(a, b, r1, r2, n) - spring->target_vrn; + + // compute velocity loss from drag + // not 100% certain this is derived correctly, though it makes sense + cpFloat w_damp = (spring->target_wrn - wrn)*spring->w_coef; + spring->target_wrn = wrn + w_damp; + + //apply_impulses(a, b, spring->r1, spring->r2, cpvmult(spring->n, v_damp*spring->nMass)); + cpFloat j_damp = w_damp*spring->iSum; + spring->jAcc += j_damp; + + a->w += j_damp*a->i_inv; + b->w -= j_damp*b->i_inv; +} + +static cpFloat +getImpulse(cpDampedRotarySpring *spring) +{ + return spring->jAcc; +} + +static const cpConstraintClass klass = { + (cpConstraintPreStepImpl)preStep, + (cpConstraintApplyCachedImpulseImpl)applyCachedImpulse, + (cpConstraintApplyImpulseImpl)applyImpulse, + (cpConstraintGetImpulseImpl)getImpulse, +}; + +cpDampedRotarySpring * +cpDampedRotarySpringAlloc(void) +{ + return (cpDampedRotarySpring *)cpcalloc(1, sizeof(cpDampedRotarySpring)); +} + +cpDampedRotarySpring * +cpDampedRotarySpringInit(cpDampedRotarySpring *spring, cpBody *a, cpBody *b, cpFloat restAngle, cpFloat stiffness, cpFloat damping) +{ + cpConstraintInit((cpConstraint *)spring, &klass, a, b); + + spring->restAngle = restAngle; + spring->stiffness = stiffness; + spring->damping = damping; + spring->springTorqueFunc = (cpDampedRotarySpringTorqueFunc)defaultSpringTorque; + + spring->jAcc = 0.0f; + + return spring; +} + +cpConstraint * +cpDampedRotarySpringNew(cpBody *a, cpBody *b, cpFloat restAngle, cpFloat stiffness, cpFloat damping) +{ + return (cpConstraint *)cpDampedRotarySpringInit(cpDampedRotarySpringAlloc(), a, b, restAngle, stiffness, damping); +} + +cpBool +cpConstraintIsDampedRotarySpring(const cpConstraint *constraint) +{ + return (constraint->klass == &klass); +} + +cpFloat +cpDampedRotarySpringGetRestAngle(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsDampedRotarySpring(constraint), "Constraint is not a damped rotary spring."); + return ((cpDampedRotarySpring *)constraint)->restAngle; +} + +void +cpDampedRotarySpringSetRestAngle(cpConstraint *constraint, cpFloat restAngle) +{ + cpAssertHard(cpConstraintIsDampedRotarySpring(constraint), "Constraint is not a damped rotary spring."); + cpConstraintActivateBodies(constraint); + ((cpDampedRotarySpring *)constraint)->restAngle = restAngle; +} + +cpFloat +cpDampedRotarySpringGetStiffness(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsDampedRotarySpring(constraint), "Constraint is not a damped rotary spring."); + return ((cpDampedRotarySpring *)constraint)->stiffness; +} + +void +cpDampedRotarySpringSetStiffness(cpConstraint *constraint, cpFloat stiffness) +{ + cpAssertHard(cpConstraintIsDampedRotarySpring(constraint), "Constraint is not a damped rotary spring."); + cpConstraintActivateBodies(constraint); + ((cpDampedRotarySpring *)constraint)->stiffness = stiffness; +} + +cpFloat +cpDampedRotarySpringGetDamping(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsDampedRotarySpring(constraint), "Constraint is not a damped rotary spring."); + return ((cpDampedRotarySpring *)constraint)->damping; +} + +void +cpDampedRotarySpringSetDamping(cpConstraint *constraint, cpFloat damping) +{ + cpAssertHard(cpConstraintIsDampedRotarySpring(constraint), "Constraint is not a damped rotary spring."); + cpConstraintActivateBodies(constraint); + ((cpDampedRotarySpring *)constraint)->damping = damping; +} + +cpDampedRotarySpringTorqueFunc +cpDampedRotarySpringGetSpringTorqueFunc(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsDampedRotarySpring(constraint), "Constraint is not a damped rotary spring."); + return ((cpDampedRotarySpring *)constraint)->springTorqueFunc; +} + +void +cpDampedRotarySpringSetSpringTorqueFunc(cpConstraint *constraint, cpDampedRotarySpringTorqueFunc springTorqueFunc) +{ + cpAssertHard(cpConstraintIsDampedRotarySpring(constraint), "Constraint is not a damped rotary spring."); + cpConstraintActivateBodies(constraint); + ((cpDampedRotarySpring *)constraint)->springTorqueFunc = springTorqueFunc; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedSpring.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedSpring.c" new file mode 100644 index 0000000..e4d019e --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedSpring.c" @@ -0,0 +1,216 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +static cpFloat +defaultSpringForce(cpDampedSpring *spring, cpFloat dist){ + return (spring->restLength - dist)*spring->stiffness; +} + +static void +preStep(cpDampedSpring *spring, cpFloat dt) +{ + cpBody *a = spring->constraint.a; + cpBody *b = spring->constraint.b; + + spring->r1 = cpTransformVect(a->transform, cpvsub(spring->anchorA, a->cog)); + spring->r2 = cpTransformVect(b->transform, cpvsub(spring->anchorB, b->cog)); + + cpVect delta = cpvsub(cpvadd(b->p, spring->r2), cpvadd(a->p, spring->r1)); + cpFloat dist = cpvlength(delta); + spring->n = cpvmult(delta, 1.0f/(dist ? dist : INFINITY)); + + cpFloat k = k_scalar(a, b, spring->r1, spring->r2, spring->n); + cpAssertSoft(k != 0.0, "Unsolvable spring."); + spring->nMass = 1.0f/k; + + spring->target_vrn = 0.0f; + spring->v_coef = 1.0f - cpfexp(-spring->damping*dt*k); + + // apply spring force + cpFloat f_spring = spring->springForceFunc((cpConstraint *)spring, dist); + cpFloat j_spring = spring->jAcc = f_spring*dt; + apply_impulses(a, b, spring->r1, spring->r2, cpvmult(spring->n, j_spring)); +} + +static void applyCachedImpulse(cpDampedSpring *spring, cpFloat dt_coef){} + +static void +applyImpulse(cpDampedSpring *spring, cpFloat dt) +{ + cpBody *a = spring->constraint.a; + cpBody *b = spring->constraint.b; + + cpVect n = spring->n; + cpVect r1 = spring->r1; + cpVect r2 = spring->r2; + + // compute relative velocity + cpFloat vrn = normal_relative_velocity(a, b, r1, r2, n); + + // compute velocity loss from drag + cpFloat v_damp = (spring->target_vrn - vrn)*spring->v_coef; + spring->target_vrn = vrn + v_damp; + + cpFloat j_damp = v_damp*spring->nMass; + spring->jAcc += j_damp; + apply_impulses(a, b, spring->r1, spring->r2, cpvmult(spring->n, j_damp)); +} + +static cpFloat +getImpulse(cpDampedSpring *spring) +{ + return spring->jAcc; +} + +static const cpConstraintClass klass = { + (cpConstraintPreStepImpl)preStep, + (cpConstraintApplyCachedImpulseImpl)applyCachedImpulse, + (cpConstraintApplyImpulseImpl)applyImpulse, + (cpConstraintGetImpulseImpl)getImpulse, +}; + +cpDampedSpring * +cpDampedSpringAlloc(void) +{ + return (cpDampedSpring *)cpcalloc(1, sizeof(cpDampedSpring)); +} + +cpDampedSpring * +cpDampedSpringInit(cpDampedSpring *spring, cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB, cpFloat restLength, cpFloat stiffness, cpFloat damping) +{ + cpConstraintInit((cpConstraint *)spring, &klass, a, b); + + spring->anchorA = anchorA; + spring->anchorB = anchorB; + + spring->restLength = restLength; + spring->stiffness = stiffness; + spring->damping = damping; + spring->springForceFunc = (cpDampedSpringForceFunc)defaultSpringForce; + + spring->jAcc = 0.0f; + + return spring; +} + +cpConstraint * +cpDampedSpringNew(cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB, cpFloat restLength, cpFloat stiffness, cpFloat damping) +{ + return (cpConstraint *)cpDampedSpringInit(cpDampedSpringAlloc(), a, b, anchorA, anchorB, restLength, stiffness, damping); +} + +cpBool +cpConstraintIsDampedSpring(const cpConstraint *constraint) +{ + return (constraint->klass == &klass); +} + +cpVect +cpDampedSpringGetAnchorA(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsDampedSpring(constraint), "Constraint is not a damped spring."); + return ((cpDampedSpring *)constraint)->anchorA; +} + +void +cpDampedSpringSetAnchorA(cpConstraint *constraint, cpVect anchorA) +{ + cpAssertHard(cpConstraintIsDampedSpring(constraint), "Constraint is not a damped spring."); + cpConstraintActivateBodies(constraint); + ((cpDampedSpring *)constraint)->anchorA = anchorA; +} + +cpVect +cpDampedSpringGetAnchorB(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsDampedSpring(constraint), "Constraint is not a damped spring."); + return ((cpDampedSpring *)constraint)->anchorB; +} + +void +cpDampedSpringSetAnchorB(cpConstraint *constraint, cpVect anchorB) +{ + cpAssertHard(cpConstraintIsDampedSpring(constraint), "Constraint is not a damped spring."); + cpConstraintActivateBodies(constraint); + ((cpDampedSpring *)constraint)->anchorB = anchorB; +} + +cpFloat +cpDampedSpringGetRestLength(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsDampedSpring(constraint), "Constraint is not a damped spring."); + return ((cpDampedSpring *)constraint)->restLength; +} + +void +cpDampedSpringSetRestLength(cpConstraint *constraint, cpFloat restLength) +{ + cpAssertHard(cpConstraintIsDampedSpring(constraint), "Constraint is not a damped spring."); + cpConstraintActivateBodies(constraint); + ((cpDampedSpring *)constraint)->restLength = restLength; +} + +cpFloat +cpDampedSpringGetStiffness(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsDampedSpring(constraint), "Constraint is not a damped spring."); + return ((cpDampedSpring *)constraint)->stiffness; +} + +void +cpDampedSpringSetStiffness(cpConstraint *constraint, cpFloat stiffness) +{ + cpAssertHard(cpConstraintIsDampedSpring(constraint), "Constraint is not a damped spring."); + cpConstraintActivateBodies(constraint); + ((cpDampedSpring *)constraint)->stiffness = stiffness; +} + +cpFloat +cpDampedSpringGetDamping(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsDampedSpring(constraint), "Constraint is not a damped spring."); + return ((cpDampedSpring *)constraint)->damping; +} + +void +cpDampedSpringSetDamping(cpConstraint *constraint, cpFloat damping) +{ + cpAssertHard(cpConstraintIsDampedSpring(constraint), "Constraint is not a damped spring."); + cpConstraintActivateBodies(constraint); + ((cpDampedSpring *)constraint)->damping = damping; +} + +cpDampedSpringForceFunc +cpDampedSpringGetSpringForceFunc(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsDampedSpring(constraint), "Constraint is not a damped spring."); + return ((cpDampedSpring *)constraint)->springForceFunc; +} + +void +cpDampedSpringSetSpringForceFunc(cpConstraint *constraint, cpDampedSpringForceFunc springForceFunc) +{ + cpAssertHard(cpConstraintIsDampedSpring(constraint), "Constraint is not a damped spring."); + cpConstraintActivateBodies(constraint); + ((cpDampedSpring *)constraint)->springForceFunc = springForceFunc; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGearJoint.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGearJoint.c" new file mode 100644 index 0000000..3670173 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGearJoint.c" @@ -0,0 +1,145 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +static void +preStep(cpGearJoint *joint, cpFloat dt) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + // calculate moment of inertia coefficient. + joint->iSum = 1.0f/(a->i_inv*joint->ratio_inv + joint->ratio*b->i_inv); + + // calculate bias velocity + cpFloat maxBias = joint->constraint.maxBias; + joint->bias = cpfclamp(-bias_coef(joint->constraint.errorBias, dt)*(b->a*joint->ratio - a->a - joint->phase)/dt, -maxBias, maxBias); +} + +static void +applyCachedImpulse(cpGearJoint *joint, cpFloat dt_coef) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + cpFloat j = joint->jAcc*dt_coef; + a->w -= j*a->i_inv*joint->ratio_inv; + b->w += j*b->i_inv; +} + +static void +applyImpulse(cpGearJoint *joint, cpFloat dt) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + // compute relative rotational velocity + cpFloat wr = b->w*joint->ratio - a->w; + + cpFloat jMax = joint->constraint.maxForce*dt; + + // compute normal impulse + cpFloat j = (joint->bias - wr)*joint->iSum; + cpFloat jOld = joint->jAcc; + joint->jAcc = cpfclamp(jOld + j, -jMax, jMax); + j = joint->jAcc - jOld; + + // apply impulse + a->w -= j*a->i_inv*joint->ratio_inv; + b->w += j*b->i_inv; +} + +static cpFloat +getImpulse(cpGearJoint *joint) +{ + return cpfabs(joint->jAcc); +} + +static const cpConstraintClass klass = { + (cpConstraintPreStepImpl)preStep, + (cpConstraintApplyCachedImpulseImpl)applyCachedImpulse, + (cpConstraintApplyImpulseImpl)applyImpulse, + (cpConstraintGetImpulseImpl)getImpulse, +}; + +cpGearJoint * +cpGearJointAlloc(void) +{ + return (cpGearJoint *)cpcalloc(1, sizeof(cpGearJoint)); +} + +cpGearJoint * +cpGearJointInit(cpGearJoint *joint, cpBody *a, cpBody *b, cpFloat phase, cpFloat ratio) +{ + cpConstraintInit((cpConstraint *)joint, &klass, a, b); + + joint->phase = phase; + joint->ratio = ratio; + joint->ratio_inv = 1.0f/ratio; + + joint->jAcc = 0.0f; + + return joint; +} + +cpConstraint * +cpGearJointNew(cpBody *a, cpBody *b, cpFloat phase, cpFloat ratio) +{ + return (cpConstraint *)cpGearJointInit(cpGearJointAlloc(), a, b, phase, ratio); +} + +cpBool +cpConstraintIsGearJoint(const cpConstraint *constraint) +{ + return (constraint->klass == &klass); +} + +cpFloat +cpGearJointGetPhase(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsGearJoint(constraint), "Constraint is not a ratchet joint."); + return ((cpGearJoint *)constraint)->phase; +} + +void +cpGearJointSetPhase(cpConstraint *constraint, cpFloat phase) +{ + cpAssertHard(cpConstraintIsGearJoint(constraint), "Constraint is not a ratchet joint."); + cpConstraintActivateBodies(constraint); + ((cpGearJoint *)constraint)->phase = phase; +} + +cpFloat +cpGearJointGetRatio(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsGearJoint(constraint), "Constraint is not a ratchet joint."); + return ((cpGearJoint *)constraint)->ratio; +} + +void +cpGearJointSetRatio(cpConstraint *constraint, cpFloat ratio) +{ + cpAssertHard(cpConstraintIsGearJoint(constraint), "Constraint is not a ratchet joint."); + cpConstraintActivateBodies(constraint); + ((cpGearJoint *)constraint)->ratio = ratio; + ((cpGearJoint *)constraint)->ratio_inv = 1.0f/ratio; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGrooveJoint.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGrooveJoint.c" new file mode 100644 index 0000000..50d1857 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGrooveJoint.c" @@ -0,0 +1,197 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +static void +preStep(cpGrooveJoint *joint, cpFloat dt) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + // calculate endpoints in worldspace + cpVect ta = cpTransformPoint(a->transform, joint->grv_a); + cpVect tb = cpTransformPoint(a->transform, joint->grv_b); + + // calculate axis + cpVect n = cpTransformVect(a->transform, joint->grv_n); + cpFloat d = cpvdot(ta, n); + + joint->grv_tn = n; + joint->r2 = cpTransformVect(b->transform, cpvsub(joint->anchorB, b->cog)); + + // calculate tangential distance along the axis of r2 + cpFloat td = cpvcross(cpvadd(b->p, joint->r2), n); + // calculate clamping factor and r2 + if(td <= cpvcross(ta, n)){ + joint->clamp = 1.0f; + joint->r1 = cpvsub(ta, a->p); + } else if(td >= cpvcross(tb, n)){ + joint->clamp = -1.0f; + joint->r1 = cpvsub(tb, a->p); + } else { + joint->clamp = 0.0f; + joint->r1 = cpvsub(cpvadd(cpvmult(cpvperp(n), -td), cpvmult(n, d)), a->p); + } + + // Calculate mass tensor + joint->k = k_tensor(a, b, joint->r1, joint->r2); + + // calculate bias velocity + cpVect delta = cpvsub(cpvadd(b->p, joint->r2), cpvadd(a->p, joint->r1)); + joint->bias = cpvclamp(cpvmult(delta, -bias_coef(joint->constraint.errorBias, dt)/dt), joint->constraint.maxBias); +} + +static void +applyCachedImpulse(cpGrooveJoint *joint, cpFloat dt_coef) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + apply_impulses(a, b, joint->r1, joint->r2, cpvmult(joint->jAcc, dt_coef)); +} + +static inline cpVect +grooveConstrain(cpGrooveJoint *joint, cpVect j, cpFloat dt){ + cpVect n = joint->grv_tn; + cpVect jClamp = (joint->clamp*cpvcross(j, n) > 0.0f) ? j : cpvproject(j, n); + return cpvclamp(jClamp, joint->constraint.maxForce*dt); +} + +static void +applyImpulse(cpGrooveJoint *joint, cpFloat dt) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + cpVect r1 = joint->r1; + cpVect r2 = joint->r2; + + // compute impulse + cpVect vr = relative_velocity(a, b, r1, r2); + + cpVect j = cpMat2x2Transform(joint->k, cpvsub(joint->bias, vr)); + cpVect jOld = joint->jAcc; + joint->jAcc = grooveConstrain(joint, cpvadd(jOld, j), dt); + j = cpvsub(joint->jAcc, jOld); + + // apply impulse + apply_impulses(a, b, joint->r1, joint->r2, j); +} + +static cpFloat +getImpulse(cpGrooveJoint *joint) +{ + return cpvlength(joint->jAcc); +} + +static const cpConstraintClass klass = { + (cpConstraintPreStepImpl)preStep, + (cpConstraintApplyCachedImpulseImpl)applyCachedImpulse, + (cpConstraintApplyImpulseImpl)applyImpulse, + (cpConstraintGetImpulseImpl)getImpulse, +}; + +cpGrooveJoint * +cpGrooveJointAlloc(void) +{ + return (cpGrooveJoint *)cpcalloc(1, sizeof(cpGrooveJoint)); +} + +cpGrooveJoint * +cpGrooveJointInit(cpGrooveJoint *joint, cpBody *a, cpBody *b, cpVect groove_a, cpVect groove_b, cpVect anchorB) +{ + cpConstraintInit((cpConstraint *)joint, &klass, a, b); + + joint->grv_a = groove_a; + joint->grv_b = groove_b; + joint->grv_n = cpvperp(cpvnormalize(cpvsub(groove_b, groove_a))); + joint->anchorB = anchorB; + + joint->jAcc = cpvzero; + + return joint; +} + +cpConstraint * +cpGrooveJointNew(cpBody *a, cpBody *b, cpVect groove_a, cpVect groove_b, cpVect anchorB) +{ + return (cpConstraint *)cpGrooveJointInit(cpGrooveJointAlloc(), a, b, groove_a, groove_b, anchorB); +} + +cpBool +cpConstraintIsGrooveJoint(const cpConstraint *constraint) +{ + return (constraint->klass == &klass); +} + +cpVect +cpGrooveJointGetGrooveA(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsGrooveJoint(constraint), "Constraint is not a groove joint."); + return ((cpGrooveJoint *)constraint)->grv_a; +} + +void +cpGrooveJointSetGrooveA(cpConstraint *constraint, cpVect value) +{ + cpAssertHard(cpConstraintIsGrooveJoint(constraint), "Constraint is not a groove joint."); + cpGrooveJoint *g = (cpGrooveJoint *)constraint; + + g->grv_a = value; + g->grv_n = cpvperp(cpvnormalize(cpvsub(g->grv_b, value))); + + cpConstraintActivateBodies(constraint); +} + +cpVect +cpGrooveJointGetGrooveB(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsGrooveJoint(constraint), "Constraint is not a groove joint."); + return ((cpGrooveJoint *)constraint)->grv_b; +} + +void +cpGrooveJointSetGrooveB(cpConstraint *constraint, cpVect value) +{ + cpAssertHard(cpConstraintIsGrooveJoint(constraint), "Constraint is not a groove joint."); + cpGrooveJoint *g = (cpGrooveJoint *)constraint; + + g->grv_b = value; + g->grv_n = cpvperp(cpvnormalize(cpvsub(value, g->grv_a))); + + cpConstraintActivateBodies(constraint); +} + +cpVect +cpGrooveJointGetAnchorB(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsGrooveJoint(constraint), "Constraint is not a groove joint."); + return ((cpGrooveJoint *)constraint)->anchorB; +} + +void +cpGrooveJointSetAnchorB(cpConstraint *constraint, cpVect anchorB) +{ + cpAssertHard(cpConstraintIsGrooveJoint(constraint), "Constraint is not a groove joint."); + cpConstraintActivateBodies(constraint); + ((cpGrooveJoint *)constraint)->anchorB = anchorB; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHashSet.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHashSet.c" new file mode 100644 index 0000000..e098b42 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHashSet.c" @@ -0,0 +1,253 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" +#include "prime.h" + +typedef struct cpHashSetBin { + void *elt; + cpHashValue hash; + struct cpHashSetBin *next; +} cpHashSetBin; + +struct cpHashSet { + unsigned int entries, size; + + cpHashSetEqlFunc eql; + void *default_value; + + cpHashSetBin **table; + cpHashSetBin *pooledBins; + + cpArray *allocatedBuffers; +}; + +void +cpHashSetFree(cpHashSet *set) +{ + if(set){ + cpfree(set->table); + + cpArrayFreeEach(set->allocatedBuffers, cpfree); + cpArrayFree(set->allocatedBuffers); + + cpfree(set); + } +} + +cpHashSet * +cpHashSetNew(int size, cpHashSetEqlFunc eqlFunc) +{ + cpHashSet *set = (cpHashSet *)cpcalloc(1, sizeof(cpHashSet)); + + set->size = next_prime(size); + set->entries = 0; + + set->eql = eqlFunc; + set->default_value = NULL; + + set->table = (cpHashSetBin **)cpcalloc(set->size, sizeof(cpHashSetBin *)); + set->pooledBins = NULL; + + set->allocatedBuffers = cpArrayNew(0); + + return set; +} + +void +cpHashSetSetDefaultValue(cpHashSet *set, void *default_value) +{ + set->default_value = default_value; +} + +static int +setIsFull(cpHashSet *set) +{ + return (set->entries >= set->size); +} + +static void +cpHashSetResize(cpHashSet *set) +{ + // Get the next approximate doubled prime. + unsigned int newSize = next_prime(set->size + 1); + // Allocate a new table. + cpHashSetBin **newTable = (cpHashSetBin **)cpcalloc(newSize, sizeof(cpHashSetBin *)); + + // Iterate over the chains. + for(unsigned int i=0; isize; i++){ + // Rehash the bins into the new table. + cpHashSetBin *bin = set->table[i]; + while(bin){ + cpHashSetBin *next = bin->next; + + cpHashValue idx = bin->hash%newSize; + bin->next = newTable[idx]; + newTable[idx] = bin; + + bin = next; + } + } + + cpfree(set->table); + + set->table = newTable; + set->size = newSize; +} + +static inline void +recycleBin(cpHashSet *set, cpHashSetBin *bin) +{ + bin->next = set->pooledBins; + set->pooledBins = bin; + bin->elt = NULL; +} + +static cpHashSetBin * +getUnusedBin(cpHashSet *set) +{ + cpHashSetBin *bin = set->pooledBins; + + if(bin){ + set->pooledBins = bin->next; + return bin; + } else { + // Pool is exhausted, make more + int count = CP_BUFFER_BYTES/sizeof(cpHashSetBin); + cpAssertHard(count, "Internal Error: Buffer size is too small."); + + cpHashSetBin *buffer = (cpHashSetBin *)cpcalloc(1, CP_BUFFER_BYTES); + cpArrayPush(set->allocatedBuffers, buffer); + + // push all but the first one, return it instead + for(int i=1; ientries; +} + +void * +cpHashSetInsert(cpHashSet *set, cpHashValue hash, void *ptr, cpHashSetTransFunc trans, void *data) +{ + cpHashValue idx = hash%set->size; + + // Find the bin with the matching element. + cpHashSetBin *bin = set->table[idx]; + while(bin && !set->eql(ptr, bin->elt)) + bin = bin->next; + + // Create it if necessary. + if(!bin){ + bin = getUnusedBin(set); + bin->hash = hash; + bin->elt = (trans ? trans(ptr, data) : data); + + bin->next = set->table[idx]; + set->table[idx] = bin; + + set->entries++; + if(setIsFull(set)) cpHashSetResize(set); + } + + return bin->elt; +} + +void * +cpHashSetRemove(cpHashSet *set, cpHashValue hash, void *ptr) +{ + cpHashValue idx = hash%set->size; + + cpHashSetBin **prev_ptr = &set->table[idx]; + cpHashSetBin *bin = set->table[idx]; + + // Find the bin + while(bin && !set->eql(ptr, bin->elt)){ + prev_ptr = &bin->next; + bin = bin->next; + } + + // Remove it if it exists. + if(bin){ + // Update the previous linked list pointer + (*prev_ptr) = bin->next; + set->entries--; + + void *elt = bin->elt; + recycleBin(set, bin); + + return elt; + } + + return NULL; +} + +void * +cpHashSetFind(cpHashSet *set, cpHashValue hash, void *ptr) +{ + cpHashValue idx = hash%set->size; + cpHashSetBin *bin = set->table[idx]; + while(bin && !set->eql(ptr, bin->elt)) + bin = bin->next; + + return (bin ? bin->elt : set->default_value); +} + +void +cpHashSetEach(cpHashSet *set, cpHashSetIteratorFunc func, void *data) +{ + for(unsigned int i=0; isize; i++){ + cpHashSetBin *bin = set->table[i]; + while(bin){ + cpHashSetBin *next = bin->next; + func(bin->elt, data); + bin = next; + } + } +} + +void +cpHashSetFilter(cpHashSet *set, cpHashSetFilterFunc func, void *data) +{ + for(unsigned int i=0; isize; i++){ + // The rest works similarly to cpHashSetRemove() above. + cpHashSetBin **prev_ptr = &set->table[i]; + cpHashSetBin *bin = set->table[i]; + while(bin){ + cpHashSetBin *next = bin->next; + + if(func(bin->elt, data)){ + prev_ptr = &bin->next; + } else { + (*prev_ptr) = next; + + set->entries--; + recycleBin(set, bin); + } + + bin = next; + } + } +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHastySpace.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHastySpace.c" new file mode 100644 index 0000000..8cd65e4 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHastySpace.c" @@ -0,0 +1,497 @@ +// Copyright 2013 Howling Moon Software. All rights reserved. +// See http://chipmunk2d.net/legal.php for more information. + +#include +#include + +#include +//#include +#ifndef WIN32 //Pymunk on windows support + #ifndef ANDROID + #include + #endif +#endif + + +#include "chipmunk/chipmunk_private.h" +#include "chipmunk/cpHastySpace.h" + + +//MARK: ARM NEON Solver + +#if __ARM_NEON__ +#include + +// Tested and known to work fine with Clang 3.0 and GCC 4.2 +// Doesn't work with Clang 1.6, and I have no idea why. +#if defined(__clang_major__) && __clang_major__ < 3 + #error Compiler not supported. +#endif + +#if CP_USE_DOUBLES + #if !__arm64 + #error Cannot use CP_USE_DOUBLES on 32 bit ARM. + #endif + + typedef float64_t cpFloat_t; + typedef float64x2_t cpFloatx2_t; + #define vld vld1q_f64 + #define vdup_n vdupq_n_f64 + #define vst vst1q_f64 + #define vst_lane vst1q_lane_f64 + #define vadd vaddq_f64 + #define vsub vsubq_f64 + #define vpadd vpaddq_f64 + #define vmul vmulq_f64 + #define vmul_n vmulq_n_f64 + #define vneg vnegq_f64 + #define vget_lane vgetq_lane_f64 + #define vset_lane vsetq_lane_f64 + #define vmin vminq_f64 + #define vmax vmaxq_f64 + #define vrev(__a) __builtin_shufflevector(__a, __a, 1, 0) +#else + typedef float32_t cpFloat_t; + typedef float32x2_t cpFloatx2_t; + #define vld vld1_f32 + #define vdup_n vdup_n_f32 + #define vst vst1_f32 + #define vst_lane vst1_lane_f32 + #define vadd vadd_f32 + #define vsub vsub_f32 + #define vpadd vpadd_f32 + #define vmul vmul_f32 + #define vmul_n vmul_n_f32 + #define vneg vneg_f32 + #define vget_lane vget_lane_f32 + #define vset_lane vset_lane_f32 + #define vmin vmin_f32 + #define vmax vmax_f32 + #define vrev vrev64_f32 +#endif + +// TODO could probably do better here, maybe using vcreate? +// especially for the constants +// Maybe use the {} notation for GCC/Clang? +static inline cpFloatx2_t +vmake(cpFloat_t x, cpFloat_t y) +{ +// cpFloatx2_t v = {}; +// v = vset_lane(x, v, 0); +// v = vset_lane(y, v, 1); +// +// return v; + + // This might not be super compatible, but all the NEON headers use it... + return (cpFloatx2_t){x, y}; +} + +static void +cpArbiterApplyImpulse_NEON(cpArbiter *arb) +{ + cpBody *a = arb->body_a; + cpBody *b = arb->body_b; + cpFloatx2_t surface_vr = vld((cpFloat_t *)&arb->surface_vr); + cpFloatx2_t n = vld((cpFloat_t *)&arb->n); + cpFloat_t friction = arb->u; + + int numContacts = arb->count; + struct cpContact *contacts = arb->contacts; + for(int i=0; ir1); + cpFloatx2_t r2 = vld((cpFloat_t *)&con->r2); + + cpFloatx2_t perp = vmake(-1.0, 1.0); + cpFloatx2_t r1p = vmul(vrev(r1), perp); + cpFloatx2_t r2p = vmul(vrev(r2), perp); + + cpFloatx2_t vBias_a = vld((cpFloat_t *)&a->v_bias); + cpFloatx2_t vBias_b = vld((cpFloat_t *)&b->v_bias); + cpFloatx2_t wBias = vmake(a->w_bias, b->w_bias); + + cpFloatx2_t vb1 = vadd(vBias_a, vmul_n(r1p, vget_lane(wBias, 0))); + cpFloatx2_t vb2 = vadd(vBias_b, vmul_n(r2p, vget_lane(wBias, 1))); + cpFloatx2_t vbr = vsub(vb2, vb1); + + cpFloatx2_t v_a = vld((cpFloat_t *)&a->v); + cpFloatx2_t v_b = vld((cpFloat_t *)&b->v); + cpFloatx2_t w = vmake(a->w, b->w); + cpFloatx2_t v1 = vadd(v_a, vmul_n(r1p, vget_lane(w, 0))); + cpFloatx2_t v2 = vadd(v_b, vmul_n(r2p, vget_lane(w, 1))); + cpFloatx2_t vr = vsub(v2, v1); + + cpFloatx2_t vbn_vrn = vpadd(vmul(vbr, n), vmul(vr, n)); + + cpFloatx2_t v_offset = vmake(con->bias, -con->bounce); + cpFloatx2_t jOld = vmake(con->jBias, con->jnAcc); + cpFloatx2_t jbn_jn = vmul_n(vsub(v_offset, vbn_vrn), con->nMass); + jbn_jn = vmax(vadd(jOld, jbn_jn), vdup_n(0.0)); + cpFloatx2_t jApply = vsub(jbn_jn, jOld); + + cpFloatx2_t t = vmul(vrev(n), perp); + cpFloatx2_t vrt_tmp = vmul(vadd(vr, surface_vr), t); + cpFloatx2_t vrt = vpadd(vrt_tmp, vrt_tmp); + + cpFloatx2_t jtOld = {}; jtOld = vset_lane(con->jtAcc, jtOld, 0); + cpFloatx2_t jtMax = vrev(vmul_n(jbn_jn, friction)); + cpFloatx2_t jt = vmul_n(vrt, -con->tMass); + jt = vmax(vneg(jtMax), vmin(vadd(jtOld, jt), jtMax)); + cpFloatx2_t jtApply = vsub(jt, jtOld); + + cpFloatx2_t i_inv = vmake(-a->i_inv, b->i_inv); + cpFloatx2_t nperp = vmake(1.0, -1.0); + + cpFloatx2_t jBias = vmul_n(n, vget_lane(jApply, 0)); + cpFloatx2_t jBiasCross = vmul(vrev(jBias), nperp); + cpFloatx2_t biasCrosses = vpadd(vmul(r1, jBiasCross), vmul(r2, jBiasCross)); + wBias = vadd(wBias, vmul(i_inv, biasCrosses)); + + vBias_a = vsub(vBias_a, vmul_n(jBias, a->m_inv)); + vBias_b = vadd(vBias_b, vmul_n(jBias, b->m_inv)); + + cpFloatx2_t j = vadd(vmul_n(n, vget_lane(jApply, 1)), vmul_n(t, vget_lane(jtApply, 0))); + cpFloatx2_t jCross = vmul(vrev(j), nperp); + cpFloatx2_t crosses = vpadd(vmul(r1, jCross), vmul(r2, jCross)); + w = vadd(w, vmul(i_inv, crosses)); + + v_a = vsub(v_a, vmul_n(j, a->m_inv)); + v_b = vadd(v_b, vmul_n(j, b->m_inv)); + + // TODO would moving these earlier help pipeline them better? + vst((cpFloat_t *)&a->v_bias, vBias_a); + vst((cpFloat_t *)&b->v_bias, vBias_b); + vst_lane((cpFloat_t *)&a->w_bias, wBias, 0); + vst_lane((cpFloat_t *)&b->w_bias, wBias, 1); + + vst((cpFloat_t *)&a->v, v_a); + vst((cpFloat_t *)&b->v, v_b); + vst_lane((cpFloat_t *)&a->w, w, 0); + vst_lane((cpFloat_t *)&b->w, w, 1); + + vst_lane((cpFloat_t *)&con->jBias, jbn_jn, 0); + vst_lane((cpFloat_t *)&con->jnAcc, jbn_jn, 1); + vst_lane((cpFloat_t *)&con->jtAcc, jt, 0); + } +} + +#endif + +//MARK: PThreads + +// Right now using more than 2 threads probably wont help your performance any. +// If you are using a ridiculous number of iterations it could help though. +#define MAX_THREADS 2 + +struct ThreadContext { + pthread_t thread; + cpHastySpace *space; + unsigned long thread_num; +}; + +typedef void (*cpHastySpaceWorkFunction)(cpSpace *space, unsigned long worker, unsigned long worker_count); + +struct cpHastySpace { + cpSpace space; + + // Number of worker threads (including the main thread) + unsigned long num_threads; + + // Number of worker threads currently executing. (also including the main thread) + unsigned long num_working; + + // Number of constraints (plus contacts) that must exist per step to start the worker threads. + unsigned long constraint_count_threshold; + + pthread_mutex_t mutex; + pthread_cond_t cond_work, cond_resume; + + // Work function to invoke. + cpHastySpaceWorkFunction work; + + struct ThreadContext workers[MAX_THREADS - 1]; +}; + +static void * +WorkerThreadLoop(struct ThreadContext *context) +{ + cpHastySpace *hasty = context->space; + + unsigned long thread = context->thread_num; + unsigned long num_threads = hasty->num_threads; + + for(;;){ + pthread_mutex_lock(&hasty->mutex); { + if(--hasty->num_working == 0){ + pthread_cond_signal(&hasty->cond_resume); + } + + pthread_cond_wait(&hasty->cond_work, &hasty->mutex); + } pthread_mutex_unlock(&hasty->mutex); + + cpHastySpaceWorkFunction func = hasty->work; + if(func){ + hasty->work(&hasty->space, thread, num_threads); + } else { + break; + } + } + + return NULL; +} + +static void +RunWorkers(cpHastySpace *hasty, cpHastySpaceWorkFunction func) +{ + hasty->num_working = hasty->num_threads - 1; + hasty->work = func; + + if(hasty->num_working > 0){ + pthread_mutex_lock(&hasty->mutex); { + pthread_cond_broadcast(&hasty->cond_work); + } pthread_mutex_unlock(&hasty->mutex); + + func((cpSpace *)hasty, 0, hasty->num_threads); + + pthread_mutex_lock(&hasty->mutex); { + if(hasty->num_working > 0){ + pthread_cond_wait(&hasty->cond_resume, &hasty->mutex); + } + } pthread_mutex_unlock(&hasty->mutex); + } else { + func((cpSpace *)hasty, 0, hasty->num_threads); + } + + hasty->work = NULL; +} + +static void +Solver(cpSpace *space, unsigned long worker, unsigned long worker_count) +{ + cpArray *constraints = space->constraints; + cpArray *arbiters = space->arbiters; + + cpFloat dt = space->curr_dt; + unsigned long iterations = (space->iterations + worker_count - 1)/worker_count; + + for(unsigned long i=0; inum; j++){ + cpArbiter *arb = (cpArbiter *)arbiters->arr[j]; + #ifdef __ARM_NEON__ + cpArbiterApplyImpulse_NEON(arb); + #else + cpArbiterApplyImpulse(arb); + #endif + } + + for(int j=0; jnum; j++){ + cpConstraint *constraint = (cpConstraint *)constraints->arr[j]; + constraint->klass->applyImpulse(constraint, dt); + } + } +} + +//MARK: Thread Management Functions + +static void +HaltThreads(cpHastySpace *hasty) +{ + pthread_mutex_t *mutex = &hasty->mutex; + pthread_mutex_lock(mutex); { + hasty->work = NULL; // NULL work function means break and exit + pthread_cond_broadcast(&hasty->cond_work); + } pthread_mutex_unlock(mutex); + + for(unsigned long i=0; i<(hasty->num_threads-1); i++){ + pthread_join(hasty->workers[i].thread, NULL); + } +} + +void +cpHastySpaceSetThreads(cpSpace *space, unsigned long threads) +{ +#if TARGET_IPHONE_SIMULATOR == 1 + // Individual values appear to be written non-atomically when compiled as debug for the simulator. + // No idea why, so threads are disabled. + threads = 1; +#endif + + cpHastySpace *hasty = (cpHastySpace *)space; + HaltThreads(hasty); + +#ifdef __APPLE__ + if(threads == 0){ + size_t size = sizeof(threads); + sysctlbyname("hw.ncpu", &threads, &size, NULL, 0); + } +#else + if(threads == 0) threads = 1; +#endif + + hasty->num_threads = (threads < MAX_THREADS ? threads : MAX_THREADS); + hasty->num_working = hasty->num_threads - 1; + + // Create the worker threads and wait for them to signal ready. + if(hasty->num_working > 0){ + pthread_mutex_lock(&hasty->mutex); + for(unsigned long i=0; i<(hasty->num_threads-1); i++){ + hasty->workers[i].space = hasty; + hasty->workers[i].thread_num = i + 1; + + pthread_create(&hasty->workers[i].thread, NULL, (void *)WorkerThreadLoop, &hasty->workers[i]); + } + + pthread_cond_wait(&hasty->cond_resume, &hasty->mutex); + pthread_mutex_unlock(&hasty->mutex); + } +} + +unsigned long +cpHastySpaceGetThreads(cpSpace *space) +{ + return ((cpHastySpace *)space)->num_threads; +} + +//MARK: Overriden cpSpace Functions. + +cpSpace * +cpHastySpaceNew(void) +{ + cpHastySpace *hasty = (cpHastySpace *)cpcalloc(1, sizeof(cpHastySpace)); + cpSpaceInit((cpSpace *)hasty); + + pthread_mutex_init(&hasty->mutex, NULL); + pthread_cond_init(&hasty->cond_work, NULL); + pthread_cond_init(&hasty->cond_resume, NULL); + + // TODO magic number, should test this more thoroughly. + hasty->constraint_count_threshold = 50; + + // Default to 1 thread for determinism. + hasty->num_threads = 1; + cpHastySpaceSetThreads((cpSpace *)hasty, 1); + + return (cpSpace *)hasty; +} + +void +cpHastySpaceFree(cpSpace *space) +{ + cpHastySpace *hasty = (cpHastySpace *)space; + + HaltThreads(hasty); + + pthread_mutex_destroy(&hasty->mutex); + pthread_cond_destroy(&hasty->cond_work); + pthread_cond_destroy(&hasty->cond_resume); + + cpSpaceFree(space); +} + +void +cpHastySpaceStep(cpSpace *space, cpFloat dt) +{ + // don't step if the timestep is 0! + if(dt == 0.0f) return; + + space->stamp++; + + cpFloat prev_dt = space->curr_dt; + space->curr_dt = dt; + + cpArray *bodies = space->dynamicBodies; + cpArray *constraints = space->constraints; + cpArray *arbiters = space->arbiters; + + // Reset and empty the arbiter list. + for(int i=0; inum; i++){ + cpArbiter *arb = (cpArbiter *)arbiters->arr[i]; + arb->state = CP_ARBITER_STATE_NORMAL; + + // If both bodies are awake, unthread the arbiter from the contact graph. + if(!cpBodyIsSleeping(arb->body_a) && !cpBodyIsSleeping(arb->body_b)){ + cpArbiterUnthread(arb); + } + } + arbiters->num = 0; + + cpSpaceLock(space); { + // Integrate positions + for(int i=0; inum; i++){ + cpBody *body = (cpBody *)bodies->arr[i]; + body->position_func(body, dt); + } + + // Find colliding pairs. + cpSpacePushFreshContactBuffer(space); + cpSpatialIndexEach(space->dynamicShapes, (cpSpatialIndexIteratorFunc)cpShapeUpdateFunc, NULL); + cpSpatialIndexReindexQuery(space->dynamicShapes, (cpSpatialIndexQueryFunc)cpSpaceCollideShapes, space); + } cpSpaceUnlock(space, cpFalse); + + // Rebuild the contact graph (and detect sleeping components if sleeping is enabled) + cpSpaceProcessComponents(space, dt); + + cpSpaceLock(space); { + // Clear out old cached arbiters and call separate callbacks + cpHashSetFilter(space->cachedArbiters, (cpHashSetFilterFunc)cpSpaceArbiterSetFilter, space); + + // Prestep the arbiters and constraints. + cpFloat slop = space->collisionSlop; + cpFloat biasCoef = 1.0f - cpfpow(space->collisionBias, dt); + for(int i=0; inum; i++){ + cpArbiterPreStep((cpArbiter *)arbiters->arr[i], dt, slop, biasCoef); + } + + for(int i=0; inum; i++){ + cpConstraint *constraint = (cpConstraint *)constraints->arr[i]; + + cpConstraintPreSolveFunc preSolve = constraint->preSolve; + if(preSolve) preSolve(constraint, space); + + constraint->klass->preStep(constraint, dt); + } + + // Integrate velocities. + cpFloat damping = cpfpow(space->damping, dt); + cpVect gravity = space->gravity; + for(int i=0; inum; i++){ + cpBody *body = (cpBody *)bodies->arr[i]; + body->velocity_func(body, gravity, damping, dt); + } + + // Apply cached impulses + cpFloat dt_coef = (prev_dt == 0.0f ? 0.0f : dt/prev_dt); + for(int i=0; inum; i++){ + cpArbiterApplyCachedImpulse((cpArbiter *)arbiters->arr[i], dt_coef); + } + + for(int i=0; inum; i++){ + cpConstraint *constraint = (cpConstraint *)constraints->arr[i]; + constraint->klass->applyCachedImpulse(constraint, dt_coef); + } + + // Run the impulse solver. + cpHastySpace *hasty = (cpHastySpace *)space; + if((unsigned long)(arbiters->num + constraints->num) > hasty->constraint_count_threshold){ + RunWorkers(hasty, Solver); + } else { + Solver(space, 0, 1); + } + + // Run the constraint post-solve callbacks + for(int i=0; inum; i++){ + cpConstraint *constraint = (cpConstraint *)constraints->arr[i]; + + cpConstraintPostSolveFunc postSolve = constraint->postSolve; + if(postSolve) postSolve(constraint, space); + } + + // run the post-solve callbacks + for(int i=0; inum; i++){ + cpArbiter *arb = (cpArbiter *) arbiters->arr[i]; + + cpCollisionHandler *handler = arb->handler; + handler->postSolveFunc(arb, space, handler->userData); + } + } cpSpaceUnlock(space, cpTrue); +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpMarch.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpMarch.c" new file mode 100644 index 0000000..c666bd2 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpMarch.c" @@ -0,0 +1,157 @@ +// Copyright 2013 Howling Moon Software. All rights reserved. +// See http://chipmunk2d.net/legal.php for more information. + +#include +#include +#include + +#include "chipmunk/chipmunk.h" +#include "chipmunk/cpMarch.h" + + +typedef void (*cpMarchCellFunc)( + cpFloat t, cpFloat a, cpFloat b, cpFloat c, cpFloat d, + cpFloat x0, cpFloat x1, cpFloat y0, cpFloat y1, + cpMarchSegmentFunc segment, void *segment_data +); + +// The looping and sample caching code is shared between cpMarchHard() and cpMarchSoft(). +static void +cpMarchCells( + cpBB bb, unsigned long x_samples, unsigned long y_samples, cpFloat t, + cpMarchSegmentFunc segment, void *segment_data, + cpMarchSampleFunc sample, void *sample_data, + cpMarchCellFunc cell +){ + cpFloat x_denom = 1.0/(cpFloat)(x_samples - 1); + cpFloat y_denom = 1.0/(cpFloat)(y_samples - 1); + + // TODO range assertions and short circuit for 0 sized windows. + + // Keep a copy of the previous row to avoid double lookups. + cpFloat *buffer = (cpFloat *)cpcalloc(x_samples, sizeof(cpFloat)); + for(unsigned long i=0; it)<<0 | (b>t)<<1 | (c>t)<<2 | (d>t)<<3){ + case 0x1: seg(cpv(x0, midlerp(y0,y1,a,c,t)), cpv(midlerp(x0,x1,a,b,t), y0), segment, segment_data); break; + case 0x2: seg(cpv(midlerp(x0,x1,a,b,t), y0), cpv(x1, midlerp(y0,y1,b,d,t)), segment, segment_data); break; + case 0x3: seg(cpv(x0, midlerp(y0,y1,a,c,t)), cpv(x1, midlerp(y0,y1,b,d,t)), segment, segment_data); break; + case 0x4: seg(cpv(midlerp(x0,x1,c,d,t), y1), cpv(x0, midlerp(y0,y1,a,c,t)), segment, segment_data); break; + case 0x5: seg(cpv(midlerp(x0,x1,c,d,t), y1), cpv(midlerp(x0,x1,a,b,t), y0), segment, segment_data); break; + case 0x6: seg(cpv(midlerp(x0,x1,a,b,t), y0), cpv(x1, midlerp(y0,y1,b,d,t)), segment, segment_data); + seg(cpv(midlerp(x0,x1,c,d,t), y1), cpv(x0, midlerp(y0,y1,a,c,t)), segment, segment_data); break; + case 0x7: seg(cpv(midlerp(x0,x1,c,d,t), y1), cpv(x1, midlerp(y0,y1,b,d,t)), segment, segment_data); break; + case 0x8: seg(cpv(x1, midlerp(y0,y1,b,d,t)), cpv(midlerp(x0,x1,c,d,t), y1), segment, segment_data); break; + case 0x9: seg(cpv(x0, midlerp(y0,y1,a,c,t)), cpv(midlerp(x0,x1,a,b,t), y0), segment, segment_data); + seg(cpv(x1, midlerp(y0,y1,b,d,t)), cpv(midlerp(x0,x1,c,d,t), y1), segment, segment_data); break; + case 0xA: seg(cpv(midlerp(x0,x1,a,b,t), y0), cpv(midlerp(x0,x1,c,d,t), y1), segment, segment_data); break; + case 0xB: seg(cpv(x0, midlerp(y0,y1,a,c,t)), cpv(midlerp(x0,x1,c,d,t), y1), segment, segment_data); break; + case 0xC: seg(cpv(x1, midlerp(y0,y1,b,d,t)), cpv(x0, midlerp(y0,y1,a,c,t)), segment, segment_data); break; + case 0xD: seg(cpv(x1, midlerp(y0,y1,b,d,t)), cpv(midlerp(x0,x1,a,b,t), y0), segment, segment_data); break; + case 0xE: seg(cpv(midlerp(x0,x1,a,b,t), y0), cpv(x0, midlerp(y0,y1,a,c,t)), segment, segment_data); break; + default: break; // 0x0 and 0xF + } +} + +void +cpMarchSoft( + cpBB bb, unsigned long x_samples, unsigned long y_samples, cpFloat t, + cpMarchSegmentFunc segment, void *segment_data, + cpMarchSampleFunc sample, void *sample_data +){ + cpMarchCells(bb, x_samples, y_samples, t, segment, segment_data, sample, sample_data, cpMarchCellSoft); +} + + +// TODO should flip this around eventually. +static inline void +segs(cpVect a, cpVect b, cpVect c, cpMarchSegmentFunc f, void *data) +{ + seg(b, c, f, data); + seg(a, b, f, data); +} + +static void +cpMarchCellHard( + cpFloat t, cpFloat a, cpFloat b, cpFloat c, cpFloat d, + cpFloat x0, cpFloat x1, cpFloat y0, cpFloat y1, + cpMarchSegmentFunc segment, void *segment_data +){ + // midpoints + cpFloat xm = cpflerp(x0, x1, 0.5f); + cpFloat ym = cpflerp(y0, y1, 0.5f); + + switch((a>t)<<0 | (b>t)<<1 | (c>t)<<2 | (d>t)<<3){ + case 0x1: segs(cpv(x0, ym), cpv(xm, ym), cpv(xm, y0), segment, segment_data); break; + case 0x2: segs(cpv(xm, y0), cpv(xm, ym), cpv(x1, ym), segment, segment_data); break; + case 0x3: seg(cpv(x0, ym), cpv(x1, ym), segment, segment_data); break; + case 0x4: segs(cpv(xm, y1), cpv(xm, ym), cpv(x0, ym), segment, segment_data); break; + case 0x5: seg(cpv(xm, y1), cpv(xm, y0), segment, segment_data); break; + case 0x6: segs(cpv(xm, y0), cpv(xm, ym), cpv(x0, ym), segment, segment_data); + segs(cpv(xm, y1), cpv(xm, ym), cpv(x1, ym), segment, segment_data); break; + case 0x7: segs(cpv(xm, y1), cpv(xm, ym), cpv(x1, ym), segment, segment_data); break; + case 0x8: segs(cpv(x1, ym), cpv(xm, ym), cpv(xm, y1), segment, segment_data); break; + case 0x9: segs(cpv(x1, ym), cpv(xm, ym), cpv(xm, y0), segment, segment_data); + segs(cpv(x0, ym), cpv(xm, ym), cpv(xm, y1), segment, segment_data); break; + case 0xA: seg(cpv(xm, y0), cpv(xm, y1), segment, segment_data); break; + case 0xB: segs(cpv(x0, ym), cpv(xm, ym), cpv(xm, y1), segment, segment_data); break; + case 0xC: seg(cpv(x1, ym), cpv(x0, ym), segment, segment_data); break; + case 0xD: segs(cpv(x1, ym), cpv(xm, ym), cpv(xm, y0), segment, segment_data); break; + case 0xE: segs(cpv(xm, y0), cpv(xm, ym), cpv(x0, ym), segment, segment_data); break; + default: break; // 0x0 and 0xF + } +} + +void +cpMarchHard( + cpBB bb, unsigned long x_samples, unsigned long y_samples, cpFloat t, + cpMarchSegmentFunc segment, void *segment_data, + cpMarchSampleFunc sample, void *sample_data +){ + cpMarchCells(bb, x_samples, y_samples, t, segment, segment_data, sample, sample_data, cpMarchCellHard); +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPinJoint.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPinJoint.c" new file mode 100644 index 0000000..545e78b --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPinJoint.c" @@ -0,0 +1,172 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +static void +preStep(cpPinJoint *joint, cpFloat dt) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + joint->r1 = cpTransformVect(a->transform, cpvsub(joint->anchorA, a->cog)); + joint->r2 = cpTransformVect(b->transform, cpvsub(joint->anchorB, b->cog)); + + cpVect delta = cpvsub(cpvadd(b->p, joint->r2), cpvadd(a->p, joint->r1)); + cpFloat dist = cpvlength(delta); + joint->n = cpvmult(delta, 1.0f/(dist ? dist : (cpFloat)INFINITY)); + + // calculate mass normal + joint->nMass = 1.0f/k_scalar(a, b, joint->r1, joint->r2, joint->n); + + // calculate bias velocity + cpFloat maxBias = joint->constraint.maxBias; + joint->bias = cpfclamp(-bias_coef(joint->constraint.errorBias, dt)*(dist - joint->dist)/dt, -maxBias, maxBias); +} + +static void +applyCachedImpulse(cpPinJoint *joint, cpFloat dt_coef) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + cpVect j = cpvmult(joint->n, joint->jnAcc*dt_coef); + apply_impulses(a, b, joint->r1, joint->r2, j); +} + +static void +applyImpulse(cpPinJoint *joint, cpFloat dt) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + cpVect n = joint->n; + + // compute relative velocity + cpFloat vrn = normal_relative_velocity(a, b, joint->r1, joint->r2, n); + + cpFloat jnMax = joint->constraint.maxForce*dt; + + // compute normal impulse + cpFloat jn = (joint->bias - vrn)*joint->nMass; + cpFloat jnOld = joint->jnAcc; + joint->jnAcc = cpfclamp(jnOld + jn, -jnMax, jnMax); + jn = joint->jnAcc - jnOld; + + // apply impulse + apply_impulses(a, b, joint->r1, joint->r2, cpvmult(n, jn)); +} + +static cpFloat +getImpulse(cpPinJoint *joint) +{ + return cpfabs(joint->jnAcc); +} + +static const cpConstraintClass klass = { + (cpConstraintPreStepImpl)preStep, + (cpConstraintApplyCachedImpulseImpl)applyCachedImpulse, + (cpConstraintApplyImpulseImpl)applyImpulse, + (cpConstraintGetImpulseImpl)getImpulse, +}; + + +cpPinJoint * +cpPinJointAlloc(void) +{ + return (cpPinJoint *)cpcalloc(1, sizeof(cpPinJoint)); +} + +cpPinJoint * +cpPinJointInit(cpPinJoint *joint, cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB) +{ + cpConstraintInit((cpConstraint *)joint, &klass, a, b); + + joint->anchorA = anchorA; + joint->anchorB = anchorB; + + // STATIC_BODY_CHECK + cpVect p1 = (a ? cpTransformPoint(a->transform, anchorA) : anchorA); + cpVect p2 = (b ? cpTransformPoint(b->transform, anchorB) : anchorB); + joint->dist = cpvlength(cpvsub(p2, p1)); + + cpAssertWarn(joint->dist > 0.0, "You created a 0 length pin joint. A pivot joint will be much more stable."); + + joint->jnAcc = 0.0f; + + return joint; +} + +cpConstraint * +cpPinJointNew(cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB) +{ + return (cpConstraint *)cpPinJointInit(cpPinJointAlloc(), a, b, anchorA, anchorB); +} + +cpBool +cpConstraintIsPinJoint(const cpConstraint *constraint) +{ + return (constraint->klass == &klass); +} + +cpVect +cpPinJointGetAnchorA(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsPinJoint(constraint), "Constraint is not a pin joint."); + return ((cpPinJoint *)constraint)->anchorA; +} + +void +cpPinJointSetAnchorA(cpConstraint *constraint, cpVect anchorA) +{ + cpAssertHard(cpConstraintIsPinJoint(constraint), "Constraint is not a pin joint."); + cpConstraintActivateBodies(constraint); + ((cpPinJoint *)constraint)->anchorA = anchorA; +} + +cpVect +cpPinJointGetAnchorB(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsPinJoint(constraint), "Constraint is not a pin joint."); + return ((cpPinJoint *)constraint)->anchorB; +} + +void +cpPinJointSetAnchorB(cpConstraint *constraint, cpVect anchorB) +{ + cpAssertHard(cpConstraintIsPinJoint(constraint), "Constraint is not a pin joint."); + cpConstraintActivateBodies(constraint); + ((cpPinJoint *)constraint)->anchorB = anchorB; +} + +cpFloat +cpPinJointGetDist(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsPinJoint(constraint), "Constraint is not a pin joint."); + return ((cpPinJoint *)constraint)->dist; +} + +void +cpPinJointSetDist(cpConstraint *constraint, cpFloat dist) +{ + cpAssertHard(cpConstraintIsPinJoint(constraint), "Constraint is not a pin joint."); + cpConstraintActivateBodies(constraint); + ((cpPinJoint *)constraint)->dist = dist; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPivotJoint.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPivotJoint.c" new file mode 100644 index 0000000..e45ba07 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPivotJoint.c" @@ -0,0 +1,152 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +static void +preStep(cpPivotJoint *joint, cpFloat dt) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + joint->r1 = cpTransformVect(a->transform, cpvsub(joint->anchorA, a->cog)); + joint->r2 = cpTransformVect(b->transform, cpvsub(joint->anchorB, b->cog)); + + // Calculate mass tensor + joint-> k = k_tensor(a, b, joint->r1, joint->r2); + + // calculate bias velocity + cpVect delta = cpvsub(cpvadd(b->p, joint->r2), cpvadd(a->p, joint->r1)); + joint->bias = cpvclamp(cpvmult(delta, -bias_coef(joint->constraint.errorBias, dt)/dt), joint->constraint.maxBias); +} + +static void +applyCachedImpulse(cpPivotJoint *joint, cpFloat dt_coef) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + apply_impulses(a, b, joint->r1, joint->r2, cpvmult(joint->jAcc, dt_coef)); +} + +static void +applyImpulse(cpPivotJoint *joint, cpFloat dt) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + cpVect r1 = joint->r1; + cpVect r2 = joint->r2; + + // compute relative velocity + cpVect vr = relative_velocity(a, b, r1, r2); + + // compute normal impulse + cpVect j = cpMat2x2Transform(joint->k, cpvsub(joint->bias, vr)); + cpVect jOld = joint->jAcc; + joint->jAcc = cpvclamp(cpvadd(joint->jAcc, j), joint->constraint.maxForce*dt); + j = cpvsub(joint->jAcc, jOld); + + // apply impulse + apply_impulses(a, b, joint->r1, joint->r2, j); +} + +static cpFloat +getImpulse(cpConstraint *joint) +{ + return cpvlength(((cpPivotJoint *)joint)->jAcc); +} + +static const cpConstraintClass klass = { + (cpConstraintPreStepImpl)preStep, + (cpConstraintApplyCachedImpulseImpl)applyCachedImpulse, + (cpConstraintApplyImpulseImpl)applyImpulse, + (cpConstraintGetImpulseImpl)getImpulse, +}; + +cpPivotJoint * +cpPivotJointAlloc(void) +{ + return (cpPivotJoint *)cpcalloc(1, sizeof(cpPivotJoint)); +} + +cpPivotJoint * +cpPivotJointInit(cpPivotJoint *joint, cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB) +{ + cpConstraintInit((cpConstraint *)joint, &klass, a, b); + + joint->anchorA = anchorA; + joint->anchorB = anchorB; + + joint->jAcc = cpvzero; + + return joint; +} + +cpConstraint * +cpPivotJointNew2(cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB) +{ + return (cpConstraint *)cpPivotJointInit(cpPivotJointAlloc(), a, b, anchorA, anchorB); +} + +cpConstraint * +cpPivotJointNew(cpBody *a, cpBody *b, cpVect pivot) +{ + cpVect anchorA = (a ? cpBodyWorldToLocal(a, pivot) : pivot); + cpVect anchorB = (b ? cpBodyWorldToLocal(b, pivot) : pivot); + return cpPivotJointNew2(a, b, anchorA, anchorB); +} + +cpBool +cpConstraintIsPivotJoint(const cpConstraint *constraint) +{ + return (constraint->klass == &klass); +} + +cpVect +cpPivotJointGetAnchorA(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsPivotJoint(constraint), "Constraint is not a pivot joint."); + return ((cpPivotJoint *)constraint)->anchorA; +} + +void +cpPivotJointSetAnchorA(cpConstraint *constraint, cpVect anchorA) +{ + cpAssertHard(cpConstraintIsPivotJoint(constraint), "Constraint is not a pivot joint."); + cpConstraintActivateBodies(constraint); + ((cpPivotJoint *)constraint)->anchorA = anchorA; +} + +cpVect +cpPivotJointGetAnchorB(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsPivotJoint(constraint), "Constraint is not a pivot joint."); + return ((cpPivotJoint *)constraint)->anchorB; +} + +void +cpPivotJointSetAnchorB(cpConstraint *constraint, cpVect anchorB) +{ + cpAssertHard(cpConstraintIsPivotJoint(constraint), "Constraint is not a pivot joint."); + cpConstraintActivateBodies(constraint); + ((cpPivotJoint *)constraint)->anchorB = anchorB; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyShape.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyShape.c" new file mode 100644 index 0000000..db68cdc --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyShape.c" @@ -0,0 +1,323 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" +#include "chipmunk/chipmunk_unsafe.h" + +cpPolyShape * +cpPolyShapeAlloc(void) +{ + return (cpPolyShape *)cpcalloc(1, sizeof(cpPolyShape)); +} + +static void +cpPolyShapeDestroy(cpPolyShape *poly) +{ + if(poly->count > CP_POLY_SHAPE_INLINE_ALLOC){ + cpfree(poly->planes); + } +} + +static cpBB +cpPolyShapeCacheData(cpPolyShape *poly, cpTransform transform) +{ + int count = poly->count; + struct cpSplittingPlane *dst = poly->planes; + struct cpSplittingPlane *src = dst + count; + + cpFloat l = (cpFloat)INFINITY, r = -(cpFloat)INFINITY; + cpFloat b = (cpFloat)INFINITY, t = -(cpFloat)INFINITY; + + for(int i=0; ir; + return (poly->shape.bb = cpBBNew(l - radius, b - radius, r + radius, t + radius)); +} + +static void +cpPolyShapePointQuery(cpPolyShape *poly, cpVect p, cpPointQueryInfo *info){ + int count = poly->count; + struct cpSplittingPlane *planes = poly->planes; + cpFloat r = poly->r; + + cpVect v0 = planes[count - 1].v0; + cpFloat minDist = INFINITY; + cpVect closestPoint = cpvzero; + cpVect closestNormal = cpvzero; + cpBool outside = cpFalse; + + for(int i=0; i 0.0f); + + cpVect closest = cpClosetPointOnSegment(p, v0, v1); + + cpFloat dist = cpvdist(p, closest); + if(dist < minDist){ + minDist = dist; + closestPoint = closest; + closestNormal = planes[i].n; + } + + v0 = v1; + } + + cpFloat dist = (outside ? minDist : -minDist); + cpVect g = cpvmult(cpvsub(p, closestPoint), 1.0f/dist); + + info->shape = (cpShape *)poly; + info->point = cpvadd(closestPoint, cpvmult(g, r)); + info->distance = dist - r; + + // Use the normal of the closest segment if the distance is small. + info->gradient = (minDist > MAGIC_EPSILON ? g : closestNormal); +} + +static void +cpPolyShapeSegmentQuery(cpPolyShape *poly, cpVect a, cpVect b, cpFloat r2, cpSegmentQueryInfo *info) +{ + struct cpSplittingPlane *planes = poly->planes; + int count = poly->count; + cpFloat r = poly->r; + cpFloat rsum = r + r2; + + for(int i=0; ishape = (cpShape *)poly; + info->point = cpvsub(cpvlerp(a, b, t), cpvmult(n, r2)); + info->normal = n; + info->alpha = t; + } + } + + // Also check against the beveled vertexes. + if(rsum > 0.0f){ + for(int i=0; ishape, planes[i].v0, r, a, b, r2, &circle_info); + if(circle_info.alpha < info->alpha) (*info) = circle_info; + } + } +} + +static void +SetVerts(cpPolyShape *poly, int count, const cpVect *verts) +{ + poly->count = count; + if(count <= CP_POLY_SHAPE_INLINE_ALLOC){ + poly->planes = poly->_planes; + } else { + poly->planes = (struct cpSplittingPlane *)cpcalloc(2*count, sizeof(struct cpSplittingPlane)); + } + + for(int i=0; iplanes[i + count].v0 = b; + poly->planes[i + count].n = n; + } +} + +static struct cpShapeMassInfo +cpPolyShapeMassInfo(cpFloat mass, int count, const cpVect *verts, cpFloat radius) +{ + // TODO moment is approximate due to radius. + + cpVect centroid = cpCentroidForPoly(count, verts); + struct cpShapeMassInfo info = { + mass, cpMomentForPoly(1.0f, count, verts, cpvneg(centroid), radius), + centroid, + cpAreaForPoly(count, verts, radius), + }; + + return info; +} + +static const cpShapeClass polyClass = { + CP_POLY_SHAPE, + (cpShapeCacheDataImpl)cpPolyShapeCacheData, + (cpShapeDestroyImpl)cpPolyShapeDestroy, + (cpShapePointQueryImpl)cpPolyShapePointQuery, + (cpShapeSegmentQueryImpl)cpPolyShapeSegmentQuery, +}; + +cpPolyShape * +cpPolyShapeInit(cpPolyShape *poly, cpBody *body, int count, const cpVect *verts, cpTransform transform, cpFloat radius) +{ + cpVect *hullVerts = (cpVect *)alloca(count*sizeof(cpVect)); + + // Transform the verts before building the hull in case of a negative scale. + for(int i=0; ir = radius; + + return poly; +} + +cpShape * +cpPolyShapeNew(cpBody *body, int count, const cpVect *verts, cpTransform transform, cpFloat radius) +{ + return (cpShape *)cpPolyShapeInit(cpPolyShapeAlloc(), body, count, verts, transform, radius); +} + +cpShape * +cpPolyShapeNewRaw(cpBody *body, int count, const cpVect *verts, cpFloat radius) +{ + return (cpShape *)cpPolyShapeInitRaw(cpPolyShapeAlloc(), body, count, verts, radius); +} + +cpPolyShape * +cpBoxShapeInit(cpPolyShape *poly, cpBody *body, cpFloat width, cpFloat height, cpFloat radius) +{ + cpFloat hw = width/2.0f; + cpFloat hh = height/2.0f; + + return cpBoxShapeInit2(poly, body, cpBBNew(-hw, -hh, hw, hh), radius); +} + +cpPolyShape * +cpBoxShapeInit2(cpPolyShape *poly, cpBody *body, cpBB box, cpFloat radius) +{ + cpVect verts[] = { + cpv(box.r, box.b), + cpv(box.r, box.t), + cpv(box.l, box.t), + cpv(box.l, box.b), + }; + + return cpPolyShapeInitRaw(poly, body, 4, verts, radius); +} + +cpShape * +cpBoxShapeNew(cpBody *body, cpFloat width, cpFloat height, cpFloat radius) +{ + return (cpShape *)cpBoxShapeInit(cpPolyShapeAlloc(), body, width, height, radius); +} + +cpShape * +cpBoxShapeNew2(cpBody *body, cpBB box, cpFloat radius) +{ + return (cpShape *)cpBoxShapeInit2(cpPolyShapeAlloc(), body, box, radius); +} + +int +cpPolyShapeGetCount(const cpShape *shape) +{ + cpAssertHard(shape->klass == &polyClass, "Shape is not a poly shape."); + return ((cpPolyShape *)shape)->count; +} + +cpVect +cpPolyShapeGetVert(const cpShape *shape, int i) +{ + cpAssertHard(shape->klass == &polyClass, "Shape is not a poly shape."); + + int count = cpPolyShapeGetCount(shape); + cpAssertHard(0 <= i && i < count, "Index out of range."); + + return ((cpPolyShape *)shape)->planes[i + count].v0; +} + +cpFloat +cpPolyShapeGetRadius(const cpShape *shape) +{ + cpAssertHard(shape->klass == &polyClass, "Shape is not a poly shape."); + return ((cpPolyShape *)shape)->r; +} + +// Unsafe API (chipmunk_unsafe.h) + +void +cpPolyShapeSetVerts(cpShape *shape, int count, cpVect *verts, cpTransform transform) +{ + cpVect *hullVerts = (cpVect *)alloca(count*sizeof(cpVect)); + + // Transform the verts before building the hull in case of a negative scale. + for(int i=0; iklass == &polyClass, "Shape is not a poly shape."); + cpPolyShape *poly = (cpPolyShape *)shape; + cpPolyShapeDestroy(poly); + + SetVerts(poly, count, verts); + + cpFloat mass = shape->massInfo.m; + shape->massInfo = cpPolyShapeMassInfo(shape->massInfo.m, count, verts, poly->r); + if(mass > 0.0f) cpBodyAccumulateMassFromShapes(shape->body); +} + +void +cpPolyShapeSetRadius(cpShape *shape, cpFloat radius) +{ + cpAssertHard(shape->klass == &polyClass, "Shape is not a poly shape."); + cpPolyShape *poly = (cpPolyShape *)shape; + poly->r = radius; + + + // TODO radius is not handled by moment/area +// cpFloat mass = shape->massInfo.m; +// shape->massInfo = cpPolyShapeMassInfo(shape->massInfo.m, poly->count, poly->verts, poly->r); +// if(mass > 0.0f) cpBodyAccumulateMassFromShapes(shape->body); +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyline.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyline.c" new file mode 100644 index 0000000..2be7e1a --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyline.c" @@ -0,0 +1,652 @@ +// Copyright 2013 Howling Moon Software. All rights reserved. +// See http://chipmunk2d.net/legal.php for more information. + +#include +#include +#include +#include + +#include "chipmunk/chipmunk_private.h" +#include "chipmunk/cpPolyline.h" + + +static inline int Next(int i, int count){return (i+1)%count;} + +//MARK: Polylines + +#define DEFAULT_POLYLINE_CAPACITY 16 + +static int +cpPolylineSizeForCapacity(int capacity) +{ + return sizeof(cpPolyline) + capacity*sizeof(cpVect); +} + +static cpPolyline * +cpPolylineMake(int capacity) +{ + capacity = (capacity > DEFAULT_POLYLINE_CAPACITY ? capacity : DEFAULT_POLYLINE_CAPACITY); + + cpPolyline *line = (cpPolyline *)cpcalloc(1, cpPolylineSizeForCapacity(capacity)); + line->count = 0; + line->capacity = capacity; + + return line; +} + +static cpPolyline * +cpPolylineMake2(int capacity, cpVect a, cpVect b) +{ + cpPolyline *line = cpPolylineMake(capacity); + line->count = 2; + line->verts[0] = a; + line->verts[1] = b; + + return line; +} + +static cpPolyline * +cpPolylineShrink(cpPolyline *line) +{ + line->capacity = line->count; + return cprealloc(line, cpPolylineSizeForCapacity(line->count)); +} + +void +cpPolylineFree(cpPolyline *line) +{ + cpfree(line); +} + +// Grow the allocated memory for a polyline. +static cpPolyline * +cpPolylineGrow(cpPolyline *line, int count) +{ + line->count += count; + + int capacity = line->capacity; + while(line->count > capacity) capacity *= 2; + + if(line->capacity < capacity){ + line->capacity = capacity; + line = cprealloc(line, cpPolylineSizeForCapacity(capacity)); + } + + return line; +} + +// Push v onto the end of line. +static cpPolyline * +cpPolylinePush(cpPolyline *line, cpVect v) +{ + int count = line->count; + line = cpPolylineGrow(line, 1); + line->verts[count] = v; + + return line; +} + +// Push v onto the beginning of line. +static cpPolyline * +cpPolylineEnqueue(cpPolyline *line, cpVect v) +{ + // TODO could optimize this to grow in both directions. + // Probably doesn't matter though. + int count = line->count; + line = cpPolylineGrow(line, 1); + memmove(line->verts + 1, line->verts, count*sizeof(cpVect)); + line->verts[0] = v; + + return line; +} + +// Returns true if the polyline starts and ends with the same vertex. +cpBool +cpPolylineIsClosed(cpPolyline *line) +{ + return (line->count > 1 && cpveql(line->verts[0], line->verts[line->count-1])); +} + +// Check if a cpPolyline is longer than a certain length +// Takes a range which can wrap around if the polyline is looped. +static cpBool +cpPolylineIsShort(cpVect *points, int count, int start, int end, cpFloat min) +{ + cpFloat length = 0.0f; + for(int i=start; i!=end; i=Next(i, count)){ + length += cpvdist(points[i], points[Next(i, count)]); + if(length > min) return cpFalse; + } + + return cpTrue; +} + +//MARK: Polyline Simplification + +static inline cpFloat +Sharpness(cpVect a, cpVect b, cpVect c) +{ + // TODO could speed this up by caching the normals instead of calculating each twice. + return cpvdot(cpvnormalize(cpvsub(a, b)), cpvnormalize(cpvsub(c, b))); +} + +// Join similar adjacent line segments together. Works well for hard edged shapes. +// 'tol' is the minimum anglular difference in radians of a vertex. +cpPolyline * +cpPolylineSimplifyVertexes(cpPolyline *line, cpFloat tol) +{ + cpPolyline *reduced = cpPolylineMake2(0, line->verts[0], line->verts[1]); + + cpFloat minSharp = -cpfcos(tol); + + for(int i=2; icount; i++){ + cpVect vert = line->verts[i]; + cpFloat sharp = Sharpness(reduced->verts[reduced->count - 2], reduced->verts[reduced->count - 1], vert); + + if(sharp <= minSharp){ + reduced->verts[reduced->count - 1] = vert; + } else { + reduced = cpPolylinePush(reduced, vert); + } + } + + if( + cpPolylineIsClosed(line) && + Sharpness(reduced->verts[reduced->count - 2], reduced->verts[0], reduced->verts[1]) < minSharp + ){ + reduced->verts[0] = reduced->verts[reduced->count - 2]; + reduced->count--; + } + + // TODO shrink + return reduced; +} + +// Recursive function used by cpPolylineSimplifyCurves(). +static cpPolyline * +DouglasPeucker( + cpVect *verts, cpPolyline *reduced, + int length, int start, int end, + cpFloat min, cpFloat tol +){ + // Early exit if the points are adjacent + if((end - start + length)%length < 2) return reduced; + + cpVect a = verts[start]; + cpVect b = verts[end]; + + // Check if the length is below the threshold + if(cpvnear(a, b, min) && cpPolylineIsShort(verts, length, start, end, min)) return reduced; + + // Find the maximal vertex to split and recurse on + cpFloat max = 0.0; + int maxi = start; + + cpVect n = cpvnormalize(cpvperp(cpvsub(b, a))); + cpFloat d = cpvdot(n, a); + + for(int i=Next(start, length); i!=end; i=Next(i, length)){ + cpFloat dist = fabs(cpvdot(n, verts[i]) - d); + + if(dist > max){ + max = dist; + maxi = i; + } + } + + if(max > tol){ + reduced = DouglasPeucker(verts, reduced, length, start, maxi, min, tol); + reduced = cpPolylinePush(reduced, verts[maxi]); + reduced = DouglasPeucker(verts, reduced, length, maxi, end, min, tol); + } + + return reduced; +} + +// Recursively reduce the vertex count on a polyline. Works best for smooth shapes. +// 'tol' is the maximum error for the reduction. +// The reduced polyline will never be farther than this distance from the original polyline. +cpPolyline * +cpPolylineSimplifyCurves(cpPolyline *line, cpFloat tol) +{ + cpPolyline *reduced = cpPolylineMake(line->count); + + cpFloat min = tol/2.0f; + + if(cpPolylineIsClosed(line)){ + int start, end; + cpLoopIndexes(line->verts, line->count - 1, &start, &end); + + reduced = cpPolylinePush(reduced, line->verts[start]); + reduced = DouglasPeucker(line->verts, reduced, line->count - 1, start, end, min, tol); + reduced = cpPolylinePush(reduced, line->verts[end]); + reduced = DouglasPeucker(line->verts, reduced, line->count - 1, end, start, min, tol); + reduced = cpPolylinePush(reduced, line->verts[start]); + } else { + reduced = cpPolylinePush(reduced, line->verts[0]); + reduced = DouglasPeucker(line->verts, reduced, line->count, 0, line->count - 1, min, tol); + reduced = cpPolylinePush(reduced, line->verts[line->count - 1]); + } + + return cpPolylineShrink(reduced); +} + +//MARK: Polyline Sets + +cpPolylineSet * +cpPolylineSetAlloc(void) +{ + return (cpPolylineSet *)cpcalloc(1, sizeof(cpPolylineSet)); +} + +cpPolylineSet * +cpPolylineSetInit(cpPolylineSet *set) +{ + set->count = 0; + set->capacity = 8; + set->lines = cpcalloc(set->capacity, sizeof(cpPolyline)); + + return set; +} + + +cpPolylineSet * +cpPolylineSetNew(void) +{ + return cpPolylineSetInit(cpPolylineSetAlloc()); +} + +void +cpPolylineSetDestroy(cpPolylineSet *set, cpBool freePolylines) +{ + if(freePolylines){ + for(int i=0; icount; i++){ + cpPolylineFree(set->lines[i]); + } + } + + cpfree(set->lines); +} + + +void +cpPolylineSetFree(cpPolylineSet *set, cpBool freePolylines) +{ + if(set){ + cpPolylineSetDestroy(set, freePolylines); + cpfree(set); + } +} + +// Find the polyline that ends with v. +static int +cpPolylineSetFindEnds(cpPolylineSet *set, cpVect v){ + int count = set->count; + cpPolyline **lines = set->lines; + + for(int i=0; iverts[line->count - 1], v)) return i; + } + + return -1; +} + +// Find the polyline that starts with v. +static int +cpPolylineSetFindStarts(cpPolylineSet *set, cpVect v){ + int count = set->count; + cpPolyline **lines = set->lines; + + for(int i=0; iverts[0], v)) return i; + } + + return -1; +} + +// Add a new polyline to a polyline set. +static void +cpPolylineSetPush(cpPolylineSet *set, cpPolyline *line) +{ + // grow set + set->count++; + if(set->count > set->capacity){ + set->capacity *= 2; + set->lines = cprealloc(set->lines, set->capacity*sizeof(cpPolyline)); + } + + set->lines[set->count - 1] = line; +} + +// Add a new polyline to a polyline set. +static void +cpPolylineSetAdd(cpPolylineSet *set, cpVect v0, cpVect v1) +{ + cpPolylineSetPush(set, cpPolylineMake2(DEFAULT_POLYLINE_CAPACITY, v0, v1)); +} + +// Join two cpPolylines in a polyline set together. +static void +cpPolylineSetJoin(cpPolylineSet *set, int before, int after) +{ + cpPolyline *lbefore = set->lines[before]; + cpPolyline *lafter = set->lines[after]; + + // append + int count = lbefore->count; + lbefore = cpPolylineGrow(lbefore, lafter->count); + memmove(lbefore->verts + count, lafter->verts, lafter->count*sizeof(cpVect)); + set->lines[before] = lbefore; + + // delete lafter + set->count--; + cpPolylineFree(set->lines[after]); + set->lines[after] = set->lines[set->count]; +} + +// Add a segment to a polyline set. +// A segment will either start a new polyline, join two others, or add to or loop an existing polyline. +void +cpPolylineSetCollectSegment(cpVect v0, cpVect v1, cpPolylineSet *lines) +{ + int before = cpPolylineSetFindEnds(lines, v0); + int after = cpPolylineSetFindStarts(lines, v1); + + if(before >= 0 && after >= 0){ + if(before == after){ + // loop by pushing v1 onto before + lines->lines[before] = cpPolylinePush(lines->lines[before], v1); + } else { + // join before and after + cpPolylineSetJoin(lines, before, after); + } + } else if(before >= 0){ + // push v1 onto before + lines->lines[before] = cpPolylinePush(lines->lines[before], v1); + } else if(after >= 0){ + // enqueue v0 onto after + lines->lines[after] = cpPolylineEnqueue(lines->lines[after], v0); + } else { + // create new line from v0 and v1 + cpPolylineSetAdd(lines, v0, v1); + } +} + +//MARK: Convex Hull Functions + +cpPolyline * +cpPolylineToConvexHull(cpPolyline *line, cpFloat tol) +{ + cpPolyline *hull = cpPolylineMake(line->count + 1); + hull->count = cpConvexHull(line->count, line->verts, hull->verts, NULL, tol); + hull = cpPolylinePush(hull, hull->verts[0]); + + return cpPolylineShrink(hull); +} + +//MARK: Approximate Concave Decompostition + +struct Notch { + int i; + cpFloat d; + cpVect v; + cpVect n; +}; + +static cpFloat +FindSteiner(int count, cpVect *verts, struct Notch notch) +{ + cpFloat min = INFINITY; + cpFloat feature = -1.0; + + for(int i=1; i= 0.0 && dist <= min){ + min = dist; + feature = index + t; + } + } + } + + return feature; +} + +//static cpFloat +//FindSteiner2(cpVect *verts, int count, struct Notch notch) +//{ +// cpVect a = verts[(notch.i + count - 1)%count]; +// cpVect b = verts[(notch.i + 1)%count]; +// cpVect n = cpvnormalize(cpvadd(cpvnormalize(cpvsub(notch.v, a)), cpvnormalize(cpvsub(notch.v, b)))); +// +// cpFloat min = INFINITY; +// cpFloat feature = -1.0; +// +// for(int i=1; i= 0.0 && dist <= min){ +// min = dist; +// feature = index + t; +// } +// } +// } +// +// cpAssertSoft(feature >= 0.0, "No closest features detected. This is likely due to a self intersecting polygon."); +// return feature; +//} + +//struct Range {cpFloat min, max;}; +//static inline struct Range +//clip_range(cpVect delta_a, cpVect delta_b, cpVect clip) +//{ +// cpFloat da = cpvcross(delta_a, clip); +// cpFloat db = cpvcross(delta_b, clip); +// cpFloat clamp = da/(da - db); +// if(da > db){ +// return (struct Range){-INFINITY, clamp}; +// } else if(da < db){ +// return (struct Range){clamp, INFINITY}; +// } else { +// return (struct Range){-INFINITY, INFINITY}; +// } +//} +// +//static cpFloat +//FindSteiner3(cpVect *verts, int count, struct Notch notch) +//{ +// cpFloat min = INFINITY; +// cpFloat feature = -1.0; +// +// cpVect support_a = verts[(notch.i - 1 + count)%count]; +// cpVect support_b = verts[(notch.i + 1)%count]; +// +// cpVect clip_a = cpvlerp(support_a, support_b, 0.1); +// cpVect clip_b = cpvlerp(support_b, support_b, 0.9); +// +// for(int i=1; i 0.0){ +// struct Range range1 = clip_range(delta_a, delta_b, cpvsub(notch.v, clip_a)); +// struct Range range2 = clip_range(delta_a, delta_b, cpvsub(clip_b, notch.v)); +// +// cpFloat min_t = cpfmax(0.0, cpfmax(range1.min, range2.min)); +// cpFloat max_t = cpfmin(1.0, cpfmin(range1.max, range2.max)); +// +// // Ignore if the segment has been completely clipped away. +// if(min_t < max_t){ +// cpVect seg_delta = cpvsub(seg_b, seg_a); +// cpFloat closest_t = cpfclamp(cpvdot(seg_delta, cpvsub(notch.v, seg_a))/cpvlengthsq(seg_delta), min_t, max_t); +// cpVect closest = cpvlerp(seg_a, seg_b, closest_t); +// +// cpFloat dist = cpvdistsq(notch.v, closest); +// if(dist < min){ +// min = dist; +// feature = index + closest_t; +// } +// } +// } +// } +// +// cpAssertWarn(feature >= 0.0, "Internal Error: No closest features detected."); +// return feature; +//} + +//static cpBool +//VertexUnobscured(int count, cpVect *verts, int index, int notch_i) +//{ +// cpVect v = verts[notch_i]; +// cpVect n = cpvnormalize(cpvsub(verts[index], v)); +// +// for(int i=0; i= 0.0, "No closest features detected. This is likely due to a self intersecting polygon."); +// return feature; +//} + +static struct Notch +DeepestNotch(int count, cpVect *verts, int hullCount, cpVect *hullVerts, int first, cpFloat tol) +{ + struct Notch notch = {}; + int j = Next(first, count); + + for(int i=0; i notch.d){ + notch.d = depth; + notch.i = j; + notch.v = v; + notch.n = n; + } + + j = Next(j, count); + v = verts[j]; + } + + j = Next(j, count); + } + + return notch; +} + +static inline int IMAX(int a, int b){return (a > b ? a : b);} + +static void +ApproximateConcaveDecomposition(cpVect *verts, int count, cpFloat tol, cpPolylineSet *set) +{ + int first; + cpVect *hullVerts = alloca(count*sizeof(cpVect)); + int hullCount = cpConvexHull(count, verts, hullVerts, &first, 0.0); + + if(hullCount != count){ + struct Notch notch = DeepestNotch(count, verts, hullCount, hullVerts, first, tol); + + if(notch.d > tol){ + cpFloat steiner_it = FindSteiner(count, verts, notch); + + if(steiner_it >= 0.0){ + int steiner_i = (int)steiner_it; + cpVect steiner = cpvlerp(verts[steiner_i], verts[Next(steiner_i, count)], steiner_it - steiner_i); + + // Vertex counts NOT including the steiner point. + int sub1_count = (steiner_i - notch.i + count)%count + 1; + int sub2_count = count - (steiner_i - notch.i + count)%count; + cpVect *scratch = alloca((IMAX(sub1_count, sub2_count) + 1)*sizeof(cpVect)); + + for(int i=0; iverts, hullVerts, hullCount*sizeof(cpVect)); + hull->verts[hullCount] = hullVerts[0]; + hull->count = hullCount + 1; + + cpPolylineSetPush(set, hull); +} + +cpPolylineSet * +cpPolylineConvexDecomposition_BETA(cpPolyline *line, cpFloat tol) +{ + cpAssertSoft(cpPolylineIsClosed(line), "Cannot decompose an open polygon."); + cpAssertSoft(cpAreaForPoly(line->count, line->verts, 0.0) >= 0.0, "Winding is backwards. (Are you passing a hole?)"); + + cpPolylineSet *set = cpPolylineSetNew(); + ApproximateConcaveDecomposition(line->verts, line->count - 1, tol, set); + + return set; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRatchetJoint.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRatchetJoint.c" new file mode 100644 index 0000000..b3c9687 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRatchetJoint.c" @@ -0,0 +1,179 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +static void +preStep(cpRatchetJoint *joint, cpFloat dt) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + cpFloat angle = joint->angle; + cpFloat phase = joint->phase; + cpFloat ratchet = joint->ratchet; + + cpFloat delta = b->a - a->a; + cpFloat diff = angle - delta; + cpFloat pdist = 0.0f; + + if(diff*ratchet > 0.0f){ + pdist = diff; + } else { + joint->angle = cpffloor((delta - phase)/ratchet)*ratchet + phase; + } + + // calculate moment of inertia coefficient. + joint->iSum = 1.0f/(a->i_inv + b->i_inv); + + // calculate bias velocity + cpFloat maxBias = joint->constraint.maxBias; + joint->bias = cpfclamp(-bias_coef(joint->constraint.errorBias, dt)*pdist/dt, -maxBias, maxBias); + + // If the bias is 0, the joint is not at a limit. Reset the impulse. + if(!joint->bias) joint->jAcc = 0.0f; +} + +static void +applyCachedImpulse(cpRatchetJoint *joint, cpFloat dt_coef) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + cpFloat j = joint->jAcc*dt_coef; + a->w -= j*a->i_inv; + b->w += j*b->i_inv; +} + +static void +applyImpulse(cpRatchetJoint *joint, cpFloat dt) +{ + if(!joint->bias) return; // early exit + + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + // compute relative rotational velocity + cpFloat wr = b->w - a->w; + cpFloat ratchet = joint->ratchet; + + cpFloat jMax = joint->constraint.maxForce*dt; + + // compute normal impulse + cpFloat j = -(joint->bias + wr)*joint->iSum; + cpFloat jOld = joint->jAcc; + joint->jAcc = cpfclamp((jOld + j)*ratchet, 0.0f, jMax*cpfabs(ratchet))/ratchet; + j = joint->jAcc - jOld; + + // apply impulse + a->w -= j*a->i_inv; + b->w += j*b->i_inv; +} + +static cpFloat +getImpulse(cpRatchetJoint *joint) +{ + return cpfabs(joint->jAcc); +} + +static const cpConstraintClass klass = { + (cpConstraintPreStepImpl)preStep, + (cpConstraintApplyCachedImpulseImpl)applyCachedImpulse, + (cpConstraintApplyImpulseImpl)applyImpulse, + (cpConstraintGetImpulseImpl)getImpulse, +}; + +cpRatchetJoint * +cpRatchetJointAlloc(void) +{ + return (cpRatchetJoint *)cpcalloc(1, sizeof(cpRatchetJoint)); +} + +cpRatchetJoint * +cpRatchetJointInit(cpRatchetJoint *joint, cpBody *a, cpBody *b, cpFloat phase, cpFloat ratchet) +{ + cpConstraintInit((cpConstraint *)joint, &klass, a, b); + + joint->angle = 0.0f; + joint->phase = phase; + joint->ratchet = ratchet; + + // STATIC_BODY_CHECK + joint->angle = (b ? b->a : 0.0f) - (a ? a->a : 0.0f); + + return joint; +} + +cpConstraint * +cpRatchetJointNew(cpBody *a, cpBody *b, cpFloat phase, cpFloat ratchet) +{ + return (cpConstraint *)cpRatchetJointInit(cpRatchetJointAlloc(), a, b, phase, ratchet); +} + +cpBool +cpConstraintIsRatchetJoint(const cpConstraint *constraint) +{ + return (constraint->klass == &klass); +} + +cpFloat +cpRatchetJointGetAngle(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsRatchetJoint(constraint), "Constraint is not a ratchet joint."); + return ((cpRatchetJoint *)constraint)->angle; +} + +void +cpRatchetJointSetAngle(cpConstraint *constraint, cpFloat angle) +{ + cpAssertHard(cpConstraintIsRatchetJoint(constraint), "Constraint is not a ratchet joint."); + cpConstraintActivateBodies(constraint); + ((cpRatchetJoint *)constraint)->angle = angle; +} + +cpFloat +cpRatchetJointGetPhase(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsRatchetJoint(constraint), "Constraint is not a ratchet joint."); + return ((cpRatchetJoint *)constraint)->phase; +} + +void +cpRatchetJointSetPhase(cpConstraint *constraint, cpFloat phase) +{ + cpAssertHard(cpConstraintIsRatchetJoint(constraint), "Constraint is not a ratchet joint."); + cpConstraintActivateBodies(constraint); + ((cpRatchetJoint *)constraint)->phase = phase; +} +cpFloat +cpRatchetJointGetRatchet(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsRatchetJoint(constraint), "Constraint is not a ratchet joint."); + return ((cpRatchetJoint *)constraint)->ratchet; +} + +void +cpRatchetJointSetRatchet(cpConstraint *constraint, cpFloat ratchet) +{ + cpAssertHard(cpConstraintIsRatchetJoint(constraint), "Constraint is not a ratchet joint."); + cpConstraintActivateBodies(constraint); + ((cpRatchetJoint *)constraint)->ratchet = ratchet; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRobust.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRobust.c" new file mode 100644 index 0000000..57507d1 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRobust.c" @@ -0,0 +1,13 @@ +#include "chipmunk/cpRobust.h" + + +cpBool +cpCheckPointGreater(const cpVect a, const cpVect b, const cpVect c) +{ + return (b.y - a.y)*(a.x + b.x - 2*c.x) > (b.x - a.x)*(a.y + b.y - 2*c.y); +} + +cpBool +cpCheckAxis(cpVect v0, cpVect v1, cpVect p, cpVect n){ + return cpvdot(p, n) <= cpfmax(cpvdot(v0, n), cpvdot(v1, n)); +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRotaryLimitJoint.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRotaryLimitJoint.c" new file mode 100644 index 0000000..548adbe --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRotaryLimitJoint.c" @@ -0,0 +1,160 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +static void +preStep(cpRotaryLimitJoint *joint, cpFloat dt) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + cpFloat dist = b->a - a->a; + cpFloat pdist = 0.0f; + if(dist > joint->max) { + pdist = joint->max - dist; + } else if(dist < joint->min) { + pdist = joint->min - dist; + } + + // calculate moment of inertia coefficient. + joint->iSum = 1.0f/(a->i_inv + b->i_inv); + + // calculate bias velocity + cpFloat maxBias = joint->constraint.maxBias; + joint->bias = cpfclamp(-bias_coef(joint->constraint.errorBias, dt)*pdist/dt, -maxBias, maxBias); + + // If the bias is 0, the joint is not at a limit. Reset the impulse. + if(!joint->bias) joint->jAcc = 0.0f; +} + +static void +applyCachedImpulse(cpRotaryLimitJoint *joint, cpFloat dt_coef) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + cpFloat j = joint->jAcc*dt_coef; + a->w -= j*a->i_inv; + b->w += j*b->i_inv; +} + +static void +applyImpulse(cpRotaryLimitJoint *joint, cpFloat dt) +{ + if(!joint->bias) return; // early exit + + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + // compute relative rotational velocity + cpFloat wr = b->w - a->w; + + cpFloat jMax = joint->constraint.maxForce*dt; + + // compute normal impulse + cpFloat j = -(joint->bias + wr)*joint->iSum; + cpFloat jOld = joint->jAcc; + if(joint->bias < 0.0f){ + joint->jAcc = cpfclamp(jOld + j, 0.0f, jMax); + } else { + joint->jAcc = cpfclamp(jOld + j, -jMax, 0.0f); + } + j = joint->jAcc - jOld; + + // apply impulse + a->w -= j*a->i_inv; + b->w += j*b->i_inv; +} + +static cpFloat +getImpulse(cpRotaryLimitJoint *joint) +{ + return cpfabs(joint->jAcc); +} + +static const cpConstraintClass klass = { + (cpConstraintPreStepImpl)preStep, + (cpConstraintApplyCachedImpulseImpl)applyCachedImpulse, + (cpConstraintApplyImpulseImpl)applyImpulse, + (cpConstraintGetImpulseImpl)getImpulse, +}; + +cpRotaryLimitJoint * +cpRotaryLimitJointAlloc(void) +{ + return (cpRotaryLimitJoint *)cpcalloc(1, sizeof(cpRotaryLimitJoint)); +} + +cpRotaryLimitJoint * +cpRotaryLimitJointInit(cpRotaryLimitJoint *joint, cpBody *a, cpBody *b, cpFloat min, cpFloat max) +{ + cpConstraintInit((cpConstraint *)joint, &klass, a, b); + + joint->min = min; + joint->max = max; + + joint->jAcc = 0.0f; + + return joint; +} + +cpConstraint * +cpRotaryLimitJointNew(cpBody *a, cpBody *b, cpFloat min, cpFloat max) +{ + return (cpConstraint *)cpRotaryLimitJointInit(cpRotaryLimitJointAlloc(), a, b, min, max); +} + +cpBool +cpConstraintIsRotaryLimitJoint(const cpConstraint *constraint) +{ + return (constraint->klass == &klass); +} + +cpFloat +cpRotaryLimitJointGetMin(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsRotaryLimitJoint(constraint), "Constraint is not a rotary limit joint."); + return ((cpRotaryLimitJoint *)constraint)->min; +} + +void +cpRotaryLimitJointSetMin(cpConstraint *constraint, cpFloat min) +{ + cpAssertHard(cpConstraintIsRotaryLimitJoint(constraint), "Constraint is not a rotary limit joint."); + cpConstraintActivateBodies(constraint); + ((cpRotaryLimitJoint *)constraint)->min = min; +} + +cpFloat +cpRotaryLimitJointGetMax(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsRotaryLimitJoint(constraint), "Constraint is not a rotary limit joint."); + return ((cpRotaryLimitJoint *)constraint)->max; +} + +void +cpRotaryLimitJointSetMax(cpConstraint *constraint, cpFloat max) +{ + cpAssertHard(cpConstraintIsRotaryLimitJoint(constraint), "Constraint is not a rotary limit joint."); + cpConstraintActivateBodies(constraint); + ((cpRotaryLimitJoint *)constraint)->max = max; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpShape.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpShape.c" new file mode 100644 index 0000000..26d7178 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpShape.c" @@ -0,0 +1,603 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" +#include "chipmunk/chipmunk_unsafe.h" + +#define CP_DefineShapeGetter(struct, type, member, name) \ +CP_DeclareShapeGetter(struct, type, name){ \ + cpAssertHard(shape->klass == &struct##Class, "shape is not a "#struct); \ + return ((struct *)shape)->member; \ +} + +cpShape * +cpShapeInit(cpShape *shape, const cpShapeClass *klass, cpBody *body, struct cpShapeMassInfo massInfo) +{ + shape->klass = klass; + + shape->body = body; + shape->massInfo = massInfo; + + shape->sensor = 0; + + shape->e = 0.0f; + shape->u = 0.0f; + shape->surfaceV = cpvzero; + + shape->type = 0; + shape->filter.group = CP_NO_GROUP; + shape->filter.categories = CP_ALL_CATEGORIES; + shape->filter.mask = CP_ALL_CATEGORIES; + + shape->userData = NULL; + + shape->space = NULL; + + shape->next = NULL; + shape->prev = NULL; + + return shape; +} + +void +cpShapeDestroy(cpShape *shape) +{ + if(shape->klass && shape->klass->destroy) shape->klass->destroy(shape); +} + +void +cpShapeFree(cpShape *shape) +{ + if(shape){ + cpShapeDestroy(shape); + cpfree(shape); + } +} + +cpSpace * +cpShapeGetSpace(const cpShape *shape) +{ + return shape->space; +} + +cpBody * +cpShapeGetBody(const cpShape *shape) +{ + return shape->body; +} + +void +cpShapeSetBody(cpShape *shape, cpBody *body) +{ + cpAssertHard(!cpShapeActive(shape), "You cannot change the body on an active shape. You must remove the shape from the space before changing the body."); + shape->body = body; +} + +cpFloat cpShapeGetMass(cpShape *shape){ return shape->massInfo.m; } + +void +cpShapeSetMass(cpShape *shape, cpFloat mass){ + cpBody *body = shape->body; + cpBodyActivate(body); + + shape->massInfo.m = mass; + cpBodyAccumulateMassFromShapes(body); +} + +cpFloat cpShapeGetDensity(cpShape *shape){ return shape->massInfo.m/shape->massInfo.area; } +void cpShapeSetDensity(cpShape *shape, cpFloat density){ cpShapeSetMass(shape, density*shape->massInfo.area); } + +cpFloat cpShapeGetMoment(cpShape *shape){ return shape->massInfo.m*shape->massInfo.i; } +cpFloat cpShapeGetArea(cpShape *shape){ return shape->massInfo.area; } +cpVect cpShapeGetCenterOfGravity(cpShape *shape) { return shape->massInfo.cog; } + +cpBB +cpShapeGetBB(const cpShape *shape) +{ + return shape->bb; +} + +cpBool +cpShapeGetSensor(const cpShape *shape) +{ + return shape->sensor; +} + +void +cpShapeSetSensor(cpShape *shape, cpBool sensor) +{ + cpBodyActivate(shape->body); + shape->sensor = sensor; +} + +cpFloat +cpShapeGetElasticity(const cpShape *shape) +{ + return shape->e; +} + +void +cpShapeSetElasticity(cpShape *shape, cpFloat elasticity) +{ + cpAssertHard(elasticity >= 0.0f, "Elasticity must be positive and non-zero."); + cpBodyActivate(shape->body); + shape->e = elasticity; +} + +cpFloat +cpShapeGetFriction(const cpShape *shape) +{ + return shape->u; +} + +void +cpShapeSetFriction(cpShape *shape, cpFloat friction) +{ + cpAssertHard(friction >= 0.0f, "Friction must be postive and non-zero."); + cpBodyActivate(shape->body); + shape->u = friction; +} + +cpVect +cpShapeGetSurfaceVelocity(const cpShape *shape) +{ + return shape->surfaceV; +} + +void +cpShapeSetSurfaceVelocity(cpShape *shape, cpVect surfaceVelocity) +{ + cpBodyActivate(shape->body); + shape->surfaceV = surfaceVelocity; +} + +cpDataPointer +cpShapeGetUserData(const cpShape *shape) +{ + return shape->userData; +} + +void +cpShapeSetUserData(cpShape *shape, cpDataPointer userData) +{ + shape->userData = userData; +} + +cpCollisionType +cpShapeGetCollisionType(const cpShape *shape) +{ + return shape->type; +} + +void +cpShapeSetCollisionType(cpShape *shape, cpCollisionType collisionType) +{ + cpBodyActivate(shape->body); + shape->type = collisionType; +} + +cpShapeFilter +cpShapeGetFilter(const cpShape *shape) +{ + return shape->filter; +} + +void +cpShapeSetFilter(cpShape *shape, cpShapeFilter filter) +{ + cpBodyActivate(shape->body); + shape->filter = filter; +} + +cpBB +cpShapeCacheBB(cpShape *shape) +{ + return cpShapeUpdate(shape, shape->body->transform); +} + +cpBB +cpShapeUpdate(cpShape *shape, cpTransform transform) +{ + return (shape->bb = shape->klass->cacheData(shape, transform)); +} + +cpFloat +cpShapePointQuery(const cpShape *shape, cpVect p, cpPointQueryInfo *info) +{ + cpPointQueryInfo blank = {NULL, cpvzero, INFINITY, cpvzero}; + if(info){ + (*info) = blank; + } else { + info = ␣ + } + + shape->klass->pointQuery(shape, p, info); + return info->distance; +} + + +cpBool +cpShapeSegmentQuery(const cpShape *shape, cpVect a, cpVect b, cpFloat radius, cpSegmentQueryInfo *info){ + cpSegmentQueryInfo blank = {NULL, b, cpvzero, 1.0f}; + if(info){ + (*info) = blank; + } else { + info = ␣ + } + + cpPointQueryInfo nearest; + shape->klass->pointQuery(shape, a, &nearest); + if(nearest.distance <= radius){ + info->shape = shape; + info->alpha = 0.0; + info->normal = cpvnormalize(cpvsub(a, nearest.point)); + } else { + shape->klass->segmentQuery(shape, a, b, radius, info); + } + + return (info->shape != NULL); +} + +cpContactPointSet +cpShapesCollide(const cpShape *a, const cpShape *b) +{ + struct cpContact contacts[CP_MAX_CONTACTS_PER_ARBITER]; + struct cpCollisionInfo info = cpCollide(a, b, 0, contacts); + + cpContactPointSet set; + set.count = info.count; + + // cpCollideShapes() may have swapped the contact order. Flip the normal. + cpBool swapped = (a != info.a); + set.normal = (swapped ? cpvneg(info.n) : info.n); + + for(int i=0; itc = cpTransformPoint(transform, circle->c); + return cpBBNewForCircle(c, circle->r); +} + +static void +cpCircleShapePointQuery(cpCircleShape *circle, cpVect p, cpPointQueryInfo *info) +{ + cpVect delta = cpvsub(p, circle->tc); + cpFloat d = cpvlength(delta); + cpFloat r = circle->r; + + info->shape = (cpShape *)circle; + info->point = cpvadd(circle->tc, cpvmult(delta, r/d)); // TODO: div/0 + info->distance = d - r; + + // Use up for the gradient if the distance is very small. + info->gradient = (d > MAGIC_EPSILON ? cpvmult(delta, 1.0f/d) : cpv(0.0f, 1.0f)); +} + +static void +cpCircleShapeSegmentQuery(cpCircleShape *circle, cpVect a, cpVect b, cpFloat radius, cpSegmentQueryInfo *info) +{ + CircleSegmentQuery((cpShape *)circle, circle->tc, circle->r, a, b, radius, info); +} + +static struct cpShapeMassInfo +cpCircleShapeMassInfo(cpFloat mass, cpFloat radius, cpVect center) +{ + struct cpShapeMassInfo info = { + mass, cpMomentForCircle(1.0f, 0.0f, radius, cpvzero), + center, + cpAreaForCircle(0.0f, radius), + }; + + return info; +} + +static const cpShapeClass cpCircleShapeClass = { + CP_CIRCLE_SHAPE, + (cpShapeCacheDataImpl)cpCircleShapeCacheData, + NULL, + (cpShapePointQueryImpl)cpCircleShapePointQuery, + (cpShapeSegmentQueryImpl)cpCircleShapeSegmentQuery, +}; + +cpCircleShape * +cpCircleShapeInit(cpCircleShape *circle, cpBody *body, cpFloat radius, cpVect offset) +{ + circle->c = offset; + circle->r = radius; + + cpShapeInit((cpShape *)circle, &cpCircleShapeClass, body, cpCircleShapeMassInfo(0.0f, radius, offset)); + + return circle; +} + +cpShape * +cpCircleShapeNew(cpBody *body, cpFloat radius, cpVect offset) +{ + return (cpShape *)cpCircleShapeInit(cpCircleShapeAlloc(), body, radius, offset); +} + +cpVect +cpCircleShapeGetOffset(const cpShape *shape) +{ + cpAssertHard(shape->klass == &cpCircleShapeClass, "Shape is not a circle shape."); + return ((cpCircleShape *)shape)->c; +} + +cpFloat +cpCircleShapeGetRadius(const cpShape *shape) +{ + cpAssertHard(shape->klass == &cpCircleShapeClass, "Shape is not a circle shape."); + return ((cpCircleShape *)shape)->r; +} + + +cpSegmentShape * +cpSegmentShapeAlloc(void) +{ + return (cpSegmentShape *)cpcalloc(1, sizeof(cpSegmentShape)); +} + +static cpBB +cpSegmentShapeCacheData(cpSegmentShape *seg, cpTransform transform) +{ + seg->ta = cpTransformPoint(transform, seg->a); + seg->tb = cpTransformPoint(transform, seg->b); + seg->tn = cpTransformVect(transform, seg->n); + + cpFloat l,r,b,t; + + if(seg->ta.x < seg->tb.x){ + l = seg->ta.x; + r = seg->tb.x; + } else { + l = seg->tb.x; + r = seg->ta.x; + } + + if(seg->ta.y < seg->tb.y){ + b = seg->ta.y; + t = seg->tb.y; + } else { + b = seg->tb.y; + t = seg->ta.y; + } + + cpFloat rad = seg->r; + return cpBBNew(l - rad, b - rad, r + rad, t + rad); +} + +static void +cpSegmentShapePointQuery(cpSegmentShape *seg, cpVect p, cpPointQueryInfo *info) +{ + cpVect closest = cpClosetPointOnSegment(p, seg->ta, seg->tb); + + cpVect delta = cpvsub(p, closest); + cpFloat d = cpvlength(delta); + cpFloat r = seg->r; + cpVect g = cpvmult(delta, 1.0f/d); + + info->shape = (cpShape *)seg; + info->point = (d ? cpvadd(closest, cpvmult(g, r)) : closest); + info->distance = d - r; + + // Use the segment's normal if the distance is very small. + info->gradient = (d > MAGIC_EPSILON ? g : seg->n); +} + +static void +cpSegmentShapeSegmentQuery(cpSegmentShape *seg, cpVect a, cpVect b, cpFloat r2, cpSegmentQueryInfo *info) +{ + cpVect n = seg->tn; + cpFloat d = cpvdot(cpvsub(seg->ta, a), n); + cpFloat r = seg->r + r2; + + cpVect flipped_n = (d > 0.0f ? cpvneg(n) : n); + cpVect seg_offset = cpvsub(cpvmult(flipped_n, r), a); + + // Make the endpoints relative to 'a' and move them by the thickness of the segment. + cpVect seg_a = cpvadd(seg->ta, seg_offset); + cpVect seg_b = cpvadd(seg->tb, seg_offset); + cpVect delta = cpvsub(b, a); + + if(cpvcross(delta, seg_a)*cpvcross(delta, seg_b) <= 0.0f){ + cpFloat d_offset = d + (d > 0.0f ? -r : r); + cpFloat ad = -d_offset; + cpFloat bd = cpvdot(delta, n) - d_offset; + + if(ad*bd < 0.0f){ + cpFloat t = ad/(ad - bd); + + info->shape = (cpShape *)seg; + info->point = cpvsub(cpvlerp(a, b, t), cpvmult(flipped_n, r2)); + info->normal = flipped_n; + info->alpha = t; + } + } else if(r != 0.0f){ + cpSegmentQueryInfo info1 = {NULL, b, cpvzero, 1.0f}; + cpSegmentQueryInfo info2 = {NULL, b, cpvzero, 1.0f}; + CircleSegmentQuery((cpShape *)seg, seg->ta, seg->r, a, b, r2, &info1); + CircleSegmentQuery((cpShape *)seg, seg->tb, seg->r, a, b, r2, &info2); + + if(info1.alpha < info2.alpha){ + (*info) = info1; + } else { + (*info) = info2; + } + } +} + +static struct cpShapeMassInfo +cpSegmentShapeMassInfo(cpFloat mass, cpVect a, cpVect b, cpFloat r) +{ + struct cpShapeMassInfo info = { + mass, cpMomentForBox(1.0f, cpvdist(a, b) + 2.0f*r, 2.0f*r), // TODO is an approximation. + cpvlerp(a, b, 0.5f), + cpAreaForSegment(a, b, r), + }; + + return info; +} + +static const cpShapeClass cpSegmentShapeClass = { + CP_SEGMENT_SHAPE, + (cpShapeCacheDataImpl)cpSegmentShapeCacheData, + NULL, + (cpShapePointQueryImpl)cpSegmentShapePointQuery, + (cpShapeSegmentQueryImpl)cpSegmentShapeSegmentQuery, +}; + +cpSegmentShape * +cpSegmentShapeInit(cpSegmentShape *seg, cpBody *body, cpVect a, cpVect b, cpFloat r) +{ + seg->a = a; + seg->b = b; + seg->n = cpvrperp(cpvnormalize(cpvsub(b, a))); + + seg->r = r; + + seg->a_tangent = cpvzero; + seg->b_tangent = cpvzero; + + cpShapeInit((cpShape *)seg, &cpSegmentShapeClass, body, cpSegmentShapeMassInfo(0.0f, a, b, r)); + + return seg; +} + +cpShape* +cpSegmentShapeNew(cpBody *body, cpVect a, cpVect b, cpFloat r) +{ + return (cpShape *)cpSegmentShapeInit(cpSegmentShapeAlloc(), body, a, b, r); +} + +cpVect +cpSegmentShapeGetA(const cpShape *shape) +{ + cpAssertHard(shape->klass == &cpSegmentShapeClass, "Shape is not a segment shape."); + return ((cpSegmentShape *)shape)->a; +} + +cpVect +cpSegmentShapeGetB(const cpShape *shape) +{ + cpAssertHard(shape->klass == &cpSegmentShapeClass, "Shape is not a segment shape."); + return ((cpSegmentShape *)shape)->b; +} + +cpVect +cpSegmentShapeGetNormal(const cpShape *shape) +{ + cpAssertHard(shape->klass == &cpSegmentShapeClass, "Shape is not a segment shape."); + return ((cpSegmentShape *)shape)->n; +} + +cpFloat +cpSegmentShapeGetRadius(const cpShape *shape) +{ + cpAssertHard(shape->klass == &cpSegmentShapeClass, "Shape is not a segment shape."); + return ((cpSegmentShape *)shape)->r; +} + +void +cpSegmentShapeSetNeighbors(cpShape *shape, cpVect prev, cpVect next) +{ + cpAssertHard(shape->klass == &cpSegmentShapeClass, "Shape is not a segment shape."); + cpSegmentShape *seg = (cpSegmentShape *)shape; + + seg->a_tangent = cpvsub(prev, seg->a); + seg->b_tangent = cpvsub(next, seg->b); +} + +// Unsafe API (chipmunk_unsafe.h) + +// TODO setters should wake the shape up? + +void +cpCircleShapeSetRadius(cpShape *shape, cpFloat radius) +{ + cpAssertHard(shape->klass == &cpCircleShapeClass, "Shape is not a circle shape."); + cpCircleShape *circle = (cpCircleShape *)shape; + + circle->r = radius; + + cpFloat mass = shape->massInfo.m; + shape->massInfo = cpCircleShapeMassInfo(mass, circle->r, circle->c); + if(mass > 0.0f) cpBodyAccumulateMassFromShapes(shape->body); +} + +void +cpCircleShapeSetOffset(cpShape *shape, cpVect offset) +{ + cpAssertHard(shape->klass == &cpCircleShapeClass, "Shape is not a circle shape."); + cpCircleShape *circle = (cpCircleShape *)shape; + + circle->c = offset; + + cpFloat mass = shape->massInfo.m; + shape->massInfo = cpCircleShapeMassInfo(shape->massInfo.m, circle->r, circle->c); + if(mass > 0.0f) cpBodyAccumulateMassFromShapes(shape->body); +} + +void +cpSegmentShapeSetEndpoints(cpShape *shape, cpVect a, cpVect b) +{ + cpAssertHard(shape->klass == &cpSegmentShapeClass, "Shape is not a segment shape."); + cpSegmentShape *seg = (cpSegmentShape *)shape; + + seg->a = a; + seg->b = b; + seg->n = cpvperp(cpvnormalize(cpvsub(b, a))); + + cpFloat mass = shape->massInfo.m; + shape->massInfo = cpSegmentShapeMassInfo(shape->massInfo.m, seg->a, seg->b, seg->r); + if(mass > 0.0f) cpBodyAccumulateMassFromShapes(shape->body); +} + +void +cpSegmentShapeSetRadius(cpShape *shape, cpFloat radius) +{ + cpAssertHard(shape->klass == &cpSegmentShapeClass, "Shape is not a segment shape."); + cpSegmentShape *seg = (cpSegmentShape *)shape; + + seg->r = radius; + + cpFloat mass = shape->massInfo.m; + shape->massInfo = cpSegmentShapeMassInfo(shape->massInfo.m, seg->a, seg->b, seg->r); + if(mass > 0.0f) cpBodyAccumulateMassFromShapes(shape->body); +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSimpleMotor.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSimpleMotor.c" new file mode 100644 index 0000000..6d83f80 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSimpleMotor.c" @@ -0,0 +1,123 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +static void +preStep(cpSimpleMotor *joint, cpFloat dt) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + // calculate moment of inertia coefficient. + joint->iSum = 1.0f/(a->i_inv + b->i_inv); +} + +static void +applyCachedImpulse(cpSimpleMotor *joint, cpFloat dt_coef) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + cpFloat j = joint->jAcc*dt_coef; + a->w -= j*a->i_inv; + b->w += j*b->i_inv; +} + +static void +applyImpulse(cpSimpleMotor *joint, cpFloat dt) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + // compute relative rotational velocity + cpFloat wr = b->w - a->w + joint->rate; + + cpFloat jMax = joint->constraint.maxForce*dt; + + // compute normal impulse + cpFloat j = -wr*joint->iSum; + cpFloat jOld = joint->jAcc; + joint->jAcc = cpfclamp(jOld + j, -jMax, jMax); + j = joint->jAcc - jOld; + + // apply impulse + a->w -= j*a->i_inv; + b->w += j*b->i_inv; +} + +static cpFloat +getImpulse(cpSimpleMotor *joint) +{ + return cpfabs(joint->jAcc); +} + +static const cpConstraintClass klass = { + (cpConstraintPreStepImpl)preStep, + (cpConstraintApplyCachedImpulseImpl)applyCachedImpulse, + (cpConstraintApplyImpulseImpl)applyImpulse, + (cpConstraintGetImpulseImpl)getImpulse, +}; + +cpSimpleMotor * +cpSimpleMotorAlloc(void) +{ + return (cpSimpleMotor *)cpcalloc(1, sizeof(cpSimpleMotor)); +} + +cpSimpleMotor * +cpSimpleMotorInit(cpSimpleMotor *joint, cpBody *a, cpBody *b, cpFloat rate) +{ + cpConstraintInit((cpConstraint *)joint, &klass, a, b); + + joint->rate = rate; + + joint->jAcc = 0.0f; + + return joint; +} + +cpConstraint * +cpSimpleMotorNew(cpBody *a, cpBody *b, cpFloat rate) +{ + return (cpConstraint *)cpSimpleMotorInit(cpSimpleMotorAlloc(), a, b, rate); +} + +cpBool +cpConstraintIsSimpleMotor(const cpConstraint *constraint) +{ + return (constraint->klass == &klass); +} + +cpFloat +cpSimpleMotorGetRate(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsSimpleMotor(constraint), "Constraint is not a pin joint."); + return ((cpSimpleMotor *)constraint)->rate; +} + +void +cpSimpleMotorSetRate(cpConstraint *constraint, cpFloat rate) +{ + cpAssertHard(cpConstraintIsSimpleMotor(constraint), "Constraint is not a pin joint."); + cpConstraintActivateBodies(constraint); + ((cpSimpleMotor *)constraint)->rate = rate; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSlideJoint.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSlideJoint.c" new file mode 100644 index 0000000..61afe33 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSlideJoint.c" @@ -0,0 +1,195 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +static void +preStep(cpSlideJoint *joint, cpFloat dt) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + joint->r1 = cpTransformVect(a->transform, cpvsub(joint->anchorA, a->cog)); + joint->r2 = cpTransformVect(b->transform, cpvsub(joint->anchorB, b->cog)); + + cpVect delta = cpvsub(cpvadd(b->p, joint->r2), cpvadd(a->p, joint->r1)); + cpFloat dist = cpvlength(delta); + cpFloat pdist = 0.0f; + if(dist > joint->max) { + pdist = dist - joint->max; + joint->n = cpvnormalize(delta); + } else if(dist < joint->min) { + pdist = joint->min - dist; + joint->n = cpvneg(cpvnormalize(delta)); + } else { + joint->n = cpvzero; + joint->jnAcc = 0.0f; + } + + // calculate mass normal + joint->nMass = 1.0f/k_scalar(a, b, joint->r1, joint->r2, joint->n); + + // calculate bias velocity + cpFloat maxBias = joint->constraint.maxBias; + joint->bias = cpfclamp(-bias_coef(joint->constraint.errorBias, dt)*pdist/dt, -maxBias, maxBias); +} + +static void +applyCachedImpulse(cpSlideJoint *joint, cpFloat dt_coef) +{ + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + cpVect j = cpvmult(joint->n, joint->jnAcc*dt_coef); + apply_impulses(a, b, joint->r1, joint->r2, j); +} + +static void +applyImpulse(cpSlideJoint *joint, cpFloat dt) +{ + if(cpveql(joint->n, cpvzero)) return; // early exit + + cpBody *a = joint->constraint.a; + cpBody *b = joint->constraint.b; + + cpVect n = joint->n; + cpVect r1 = joint->r1; + cpVect r2 = joint->r2; + + // compute relative velocity + cpVect vr = relative_velocity(a, b, r1, r2); + cpFloat vrn = cpvdot(vr, n); + + // compute normal impulse + cpFloat jn = (joint->bias - vrn)*joint->nMass; + cpFloat jnOld = joint->jnAcc; + joint->jnAcc = cpfclamp(jnOld + jn, -joint->constraint.maxForce*dt, 0.0f); + jn = joint->jnAcc - jnOld; + + // apply impulse + apply_impulses(a, b, joint->r1, joint->r2, cpvmult(n, jn)); +} + +static cpFloat +getImpulse(cpConstraint *joint) +{ + return cpfabs(((cpSlideJoint *)joint)->jnAcc); +} + +static const cpConstraintClass klass = { + (cpConstraintPreStepImpl)preStep, + (cpConstraintApplyCachedImpulseImpl)applyCachedImpulse, + (cpConstraintApplyImpulseImpl)applyImpulse, + (cpConstraintGetImpulseImpl)getImpulse, +}; + +cpSlideJoint * +cpSlideJointAlloc(void) +{ + return (cpSlideJoint *)cpcalloc(1, sizeof(cpSlideJoint)); +} + +cpSlideJoint * +cpSlideJointInit(cpSlideJoint *joint, cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB, cpFloat min, cpFloat max) +{ + cpConstraintInit((cpConstraint *)joint, &klass, a, b); + + joint->anchorA = anchorA; + joint->anchorB = anchorB; + joint->min = min; + joint->max = max; + + joint->jnAcc = 0.0f; + + return joint; +} + +cpConstraint * +cpSlideJointNew(cpBody *a, cpBody *b, cpVect anchorA, cpVect anchorB, cpFloat min, cpFloat max) +{ + return (cpConstraint *)cpSlideJointInit(cpSlideJointAlloc(), a, b, anchorA, anchorB, min, max); +} + +cpBool +cpConstraintIsSlideJoint(const cpConstraint *constraint) +{ + return (constraint->klass == &klass); +} + +cpVect +cpSlideJointGetAnchorA(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsSlideJoint(constraint), "Constraint is not a slide joint."); + return ((cpSlideJoint *)constraint)->anchorA; +} + +void +cpSlideJointSetAnchorA(cpConstraint *constraint, cpVect anchorA) +{ + cpAssertHard(cpConstraintIsSlideJoint(constraint), "Constraint is not a slide joint."); + cpConstraintActivateBodies(constraint); + ((cpSlideJoint *)constraint)->anchorA = anchorA; +} + +cpVect +cpSlideJointGetAnchorB(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsSlideJoint(constraint), "Constraint is not a slide joint."); + return ((cpSlideJoint *)constraint)->anchorB; +} + +void +cpSlideJointSetAnchorB(cpConstraint *constraint, cpVect anchorB) +{ + cpAssertHard(cpConstraintIsSlideJoint(constraint), "Constraint is not a slide joint."); + cpConstraintActivateBodies(constraint); + ((cpSlideJoint *)constraint)->anchorB = anchorB; +} + +cpFloat +cpSlideJointGetMin(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsSlideJoint(constraint), "Constraint is not a slide joint."); + return ((cpSlideJoint *)constraint)->min; +} + +void +cpSlideJointSetMin(cpConstraint *constraint, cpFloat min) +{ + cpAssertHard(cpConstraintIsSlideJoint(constraint), "Constraint is not a slide joint."); + cpConstraintActivateBodies(constraint); + ((cpSlideJoint *)constraint)->min = min; +} + +cpFloat +cpSlideJointGetMax(const cpConstraint *constraint) +{ + cpAssertHard(cpConstraintIsSlideJoint(constraint), "Constraint is not a slide joint."); + return ((cpSlideJoint *)constraint)->max; +} + +void +cpSlideJointSetMax(cpConstraint *constraint, cpFloat max) +{ + cpAssertHard(cpConstraintIsSlideJoint(constraint), "Constraint is not a slide joint."); + cpConstraintActivateBodies(constraint); + ((cpSlideJoint *)constraint)->max = max; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpace.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpace.c" new file mode 100644 index 0000000..079752d --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpace.c" @@ -0,0 +1,700 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include +#include + +#include "chipmunk/chipmunk_private.h" + +//MARK: Contact Set Helpers + +// Equal function for arbiterSet. +static cpBool +arbiterSetEql(cpShape **shapes, cpArbiter *arb) +{ + cpShape *a = shapes[0]; + cpShape *b = shapes[1]; + + return ((a == arb->a && b == arb->b) || (b == arb->a && a == arb->b)); +} + +//MARK: Collision Handler Set HelperFunctions + +// Equals function for collisionHandlers. +static cpBool +handlerSetEql(cpCollisionHandler *check, cpCollisionHandler *pair) +{ + return ( + (check->typeA == pair->typeA && check->typeB == pair->typeB) || + (check->typeB == pair->typeA && check->typeA == pair->typeB) + ); +} + +// Transformation function for collisionHandlers. +static void * +handlerSetTrans(cpCollisionHandler *handler, void *unused) +{ + cpCollisionHandler *copy = (cpCollisionHandler *)cpcalloc(1, sizeof(cpCollisionHandler)); + memcpy(copy, handler, sizeof(cpCollisionHandler)); + + return copy; +} + +//MARK: Misc Helper Funcs + +// Default collision functions. + +static cpBool +DefaultBegin(cpArbiter *arb, cpSpace *space, void *data){ + cpBool retA = cpArbiterCallWildcardBeginA(arb, space); + cpBool retB = cpArbiterCallWildcardBeginB(arb, space); + return retA && retB; +} + +static cpBool +DefaultPreSolve(cpArbiter *arb, cpSpace *space, void *data){ + cpBool retA = cpArbiterCallWildcardPreSolveA(arb, space); + cpBool retB = cpArbiterCallWildcardPreSolveB(arb, space); + return retA && retB; +} + +static void +DefaultPostSolve(cpArbiter *arb, cpSpace *space, void *data){ + cpArbiterCallWildcardPostSolveA(arb, space); + cpArbiterCallWildcardPostSolveB(arb, space); +} + +static void +DefaultSeparate(cpArbiter *arb, cpSpace *space, void *data){ + cpArbiterCallWildcardSeparateA(arb, space); + cpArbiterCallWildcardSeparateB(arb, space); +} + +// Use the wildcard identifier since the default handler should never match any type pair. +static cpCollisionHandler cpCollisionHandlerDefault = { + CP_WILDCARD_COLLISION_TYPE, CP_WILDCARD_COLLISION_TYPE, + DefaultBegin, DefaultPreSolve, DefaultPostSolve, DefaultSeparate, NULL +}; + +static cpBool AlwaysCollide(cpArbiter *arb, cpSpace *space, void *data){return cpTrue;} +static void DoNothing(cpArbiter *arb, cpSpace *space, void *data){} + +cpCollisionHandler cpCollisionHandlerDoNothing = { + CP_WILDCARD_COLLISION_TYPE, CP_WILDCARD_COLLISION_TYPE, + AlwaysCollide, AlwaysCollide, DoNothing, DoNothing, NULL +}; + +// function to get the estimated velocity of a shape for the cpBBTree. +static cpVect ShapeVelocityFunc(cpShape *shape){return shape->body->v;} + +// Used for disposing of collision handlers. +static void FreeWrap(void *ptr, void *unused){cpfree(ptr);} + +//MARK: Memory Management Functions + +cpSpace * +cpSpaceAlloc(void) +{ + return (cpSpace *)cpcalloc(1, sizeof(cpSpace)); +} + +cpSpace* +cpSpaceInit(cpSpace *space) +{ +#ifndef NDEBUG + static cpBool done = cpFalse; + if(!done){ + printf("Initializing cpSpace - Chipmunk v%s (Debug Enabled)\n", cpVersionString); + printf("Compile with -DNDEBUG defined to disable debug mode and runtime assertion checks\n"); + done = cpTrue; + } +#endif + + space->iterations = 10; + + space->gravity = cpvzero; + space->damping = 1.0f; + + space->collisionSlop = 0.1f; + space->collisionBias = cpfpow(1.0f - 0.1f, 60.0f); + space->collisionPersistence = 3; + + space->locked = 0; + space->stamp = 0; + + space->shapeIDCounter = 0; + space->staticShapes = cpBBTreeNew((cpSpatialIndexBBFunc)cpShapeGetBB, NULL); + space->dynamicShapes = cpBBTreeNew((cpSpatialIndexBBFunc)cpShapeGetBB, space->staticShapes); + cpBBTreeSetVelocityFunc(space->dynamicShapes, (cpBBTreeVelocityFunc)ShapeVelocityFunc); + + space->allocatedBuffers = cpArrayNew(0); + + space->dynamicBodies = cpArrayNew(0); + space->staticBodies = cpArrayNew(0); + space->sleepingComponents = cpArrayNew(0); + space->rousedBodies = cpArrayNew(0); + + space->sleepTimeThreshold = INFINITY; + space->idleSpeedThreshold = 0.0f; + + space->arbiters = cpArrayNew(0); + space->pooledArbiters = cpArrayNew(0); + + space->contactBuffersHead = NULL; + space->cachedArbiters = cpHashSetNew(0, (cpHashSetEqlFunc)arbiterSetEql); + + space->constraints = cpArrayNew(0); + + space->usesWildcards = cpFalse; + memcpy(&space->defaultHandler, &cpCollisionHandlerDoNothing, sizeof(cpCollisionHandler)); + space->collisionHandlers = cpHashSetNew(0, (cpHashSetEqlFunc)handlerSetEql); + + space->postStepCallbacks = cpArrayNew(0); + space->skipPostStep = cpFalse; + + cpBody *staticBody = cpBodyInit(&space->_staticBody, 0.0f, 0.0f); + cpBodySetType(staticBody, CP_BODY_TYPE_STATIC); + cpSpaceSetStaticBody(space, staticBody); + + return space; +} + +cpSpace* +cpSpaceNew(void) +{ + return cpSpaceInit(cpSpaceAlloc()); +} + +static void cpBodyActivateWrap(cpBody *body, void *unused){cpBodyActivate(body);} + +void +cpSpaceDestroy(cpSpace *space) +{ + cpSpaceEachBody(space, (cpSpaceBodyIteratorFunc)cpBodyActivateWrap, NULL); + + cpSpatialIndexFree(space->staticShapes); + cpSpatialIndexFree(space->dynamicShapes); + + cpArrayFree(space->dynamicBodies); + cpArrayFree(space->staticBodies); + cpArrayFree(space->sleepingComponents); + cpArrayFree(space->rousedBodies); + + cpArrayFree(space->constraints); + + cpHashSetFree(space->cachedArbiters); + + cpArrayFree(space->arbiters); + cpArrayFree(space->pooledArbiters); + + if(space->allocatedBuffers){ + cpArrayFreeEach(space->allocatedBuffers, cpfree); + cpArrayFree(space->allocatedBuffers); + } + + if(space->postStepCallbacks){ + cpArrayFreeEach(space->postStepCallbacks, cpfree); + cpArrayFree(space->postStepCallbacks); + } + + if(space->collisionHandlers) cpHashSetEach(space->collisionHandlers, FreeWrap, NULL); + cpHashSetFree(space->collisionHandlers); +} + +void +cpSpaceFree(cpSpace *space) +{ + if(space){ + cpSpaceDestroy(space); + cpfree(space); + } +} + + +//MARK: Basic properties: + +int +cpSpaceGetIterations(const cpSpace *space) +{ + return space->iterations; +} + +void +cpSpaceSetIterations(cpSpace *space, int iterations) +{ + cpAssertHard(iterations > 0, "Iterations must be positive and non-zero."); + space->iterations = iterations; +} + +cpVect +cpSpaceGetGravity(const cpSpace *space) +{ + return space->gravity; +} + +void +cpSpaceSetGravity(cpSpace *space, cpVect gravity) +{ + space->gravity = gravity; + + // Wake up all of the bodies since the gravity changed. + cpArray *components = space->sleepingComponents; + for(int i=0; inum; i++){ + cpBodyActivate((cpBody *)components->arr[i]); + } +} + +cpFloat +cpSpaceGetDamping(const cpSpace *space) +{ + return space->damping; +} + +void +cpSpaceSetDamping(cpSpace *space, cpFloat damping) +{ + cpAssertHard(damping >= 0.0, "Damping must be positive."); + space->damping = damping; +} + +cpFloat +cpSpaceGetIdleSpeedThreshold(const cpSpace *space) +{ + return space->idleSpeedThreshold; +} + +void +cpSpaceSetIdleSpeedThreshold(cpSpace *space, cpFloat idleSpeedThreshold) +{ + space->idleSpeedThreshold = idleSpeedThreshold; +} + +cpFloat +cpSpaceGetSleepTimeThreshold(const cpSpace *space) +{ + return space->sleepTimeThreshold; +} + +void +cpSpaceSetSleepTimeThreshold(cpSpace *space, cpFloat sleepTimeThreshold) +{ + space->sleepTimeThreshold = sleepTimeThreshold; +} + +cpFloat +cpSpaceGetCollisionSlop(const cpSpace *space) +{ + return space->collisionSlop; +} + +void +cpSpaceSetCollisionSlop(cpSpace *space, cpFloat collisionSlop) +{ + space->collisionSlop = collisionSlop; +} + +cpFloat +cpSpaceGetCollisionBias(const cpSpace *space) +{ + return space->collisionBias; +} + +void +cpSpaceSetCollisionBias(cpSpace *space, cpFloat collisionBias) +{ + space->collisionBias = collisionBias; +} + +cpTimestamp +cpSpaceGetCollisionPersistence(const cpSpace *space) +{ + return space->collisionPersistence; +} + +void +cpSpaceSetCollisionPersistence(cpSpace *space, cpTimestamp collisionPersistence) +{ + space->collisionPersistence = collisionPersistence; +} + +cpDataPointer +cpSpaceGetUserData(const cpSpace *space) +{ + return space->userData; +} + +void +cpSpaceSetUserData(cpSpace *space, cpDataPointer userData) +{ + space->userData = userData; +} + +cpBody * +cpSpaceGetStaticBody(const cpSpace *space) +{ + return space->staticBody; +} + +cpFloat +cpSpaceGetCurrentTimeStep(const cpSpace *space) +{ + return space->curr_dt; +} + +void +cpSpaceSetStaticBody(cpSpace *space, cpBody *body) +{ + if(space->staticBody != NULL){ + cpAssertHard(space->staticBody->shapeList == NULL, "Internal Error: Changing the designated static body while the old one still had shapes attached."); + space->staticBody->space = NULL; + } + + space->staticBody = body; + body->space = space; +} + +cpBool +cpSpaceIsLocked(cpSpace *space) +{ + return (space->locked > 0); +} + +//MARK: Collision Handler Function Management + +static void +cpSpaceUseWildcardDefaultHandler(cpSpace *space) +{ + // Spaces default to using the slightly faster "do nothing" default handler until wildcards are potentially needed. + if(!space->usesWildcards){ + space->usesWildcards = cpTrue; + memcpy(&space->defaultHandler, &cpCollisionHandlerDefault, sizeof(cpCollisionHandler)); + } +} + +cpCollisionHandler *cpSpaceAddDefaultCollisionHandler(cpSpace *space) +{ + cpSpaceUseWildcardDefaultHandler(space); + return &space->defaultHandler; +} + +cpCollisionHandler *cpSpaceAddCollisionHandler(cpSpace *space, cpCollisionType a, cpCollisionType b) +{ + cpHashValue hash = CP_HASH_PAIR(a, b); + cpCollisionHandler handler = {a, b, DefaultBegin, DefaultPreSolve, DefaultPostSolve, DefaultSeparate, NULL}; + return (cpCollisionHandler*)cpHashSetInsert(space->collisionHandlers, hash, &handler, (cpHashSetTransFunc)handlerSetTrans, NULL); +} + +cpCollisionHandler * +cpSpaceAddWildcardHandler(cpSpace *space, cpCollisionType type) +{ + cpSpaceUseWildcardDefaultHandler(space); + + cpHashValue hash = CP_HASH_PAIR(type, CP_WILDCARD_COLLISION_TYPE); + cpCollisionHandler handler = {type, CP_WILDCARD_COLLISION_TYPE, AlwaysCollide, AlwaysCollide, DoNothing, DoNothing, NULL}; + return (cpCollisionHandler*)cpHashSetInsert(space->collisionHandlers, hash, &handler, (cpHashSetTransFunc)handlerSetTrans, NULL); +} + + +//MARK: Body, Shape, and Joint Management +cpShape * +cpSpaceAddShape(cpSpace *space, cpShape *shape) +{ + cpBody *body = shape->body; + + cpAssertHard(shape->space != space, "You have already added this shape to this space. You must not add it a second time."); + cpAssertHard(!shape->space, "You have already added this shape to another space. You cannot add it to a second."); +// cpAssertHard(body->space == space, "The shape's body must be added to the space before the shape."); + cpAssertSpaceUnlocked(space); + + cpBool isStatic = (cpBodyGetType(body) == CP_BODY_TYPE_STATIC); + if(!isStatic) cpBodyActivate(body); + cpBodyAddShape(body, shape); + + shape->hashid = space->shapeIDCounter++; + cpShapeUpdate(shape, body->transform); + cpSpatialIndexInsert(isStatic ? space->staticShapes : space->dynamicShapes, shape, shape->hashid); + shape->space = space; + + return shape; +} + +cpBody * +cpSpaceAddBody(cpSpace *space, cpBody *body) +{ + cpAssertHard(body->space != space, "You have already added this body to this space. You must not add it a second time."); + cpAssertHard(!body->space, "You have already added this body to another space. You cannot add it to a second."); + cpAssertSpaceUnlocked(space); + + cpArrayPush(cpSpaceArrayForBodyType(space, cpBodyGetType(body)), body); + body->space = space; + + return body; +} + +cpConstraint * +cpSpaceAddConstraint(cpSpace *space, cpConstraint *constraint) +{ + cpAssertHard(constraint->space != space, "You have already added this constraint to this space. You must not add it a second time."); + cpAssertHard(!constraint->space, "You have already added this constraint to another space. You cannot add it to a second."); + cpAssertSpaceUnlocked(space); + + cpBody *a = constraint->a, *b = constraint->b; + cpAssertHard(a != NULL && b != NULL, "Constraint is attached to a NULL body."); +// cpAssertHard(a->space == space && b->space == space, "The constraint's bodies must be added to the space before the constraint."); + + cpBodyActivate(a); + cpBodyActivate(b); + cpArrayPush(space->constraints, constraint); + + // Push onto the heads of the bodies' constraint lists + constraint->next_a = a->constraintList; a->constraintList = constraint; + constraint->next_b = b->constraintList; b->constraintList = constraint; + constraint->space = space; + + return constraint; +} + +struct arbiterFilterContext { + cpSpace *space; + cpBody *body; + cpShape *shape; +}; + +static cpBool +cachedArbitersFilter(cpArbiter *arb, struct arbiterFilterContext *context) +{ + cpShape *shape = context->shape; + cpBody *body = context->body; + + + // Match on the filter shape, or if it's NULL the filter body + if( + (body == arb->body_a && (shape == arb->a || shape == NULL)) || + (body == arb->body_b && (shape == arb->b || shape == NULL)) + ){ + // Call separate when removing shapes. + if(shape && arb->state != CP_ARBITER_STATE_CACHED){ + // Invalidate the arbiter since one of the shapes was removed. + arb->state = CP_ARBITER_STATE_INVALIDATED; + + cpCollisionHandler *handler = arb->handler; + handler->separateFunc(arb, context->space, handler->userData); + } + + cpArbiterUnthread(arb); + cpArrayDeleteObj(context->space->arbiters, arb); + cpArrayPush(context->space->pooledArbiters, arb); + + return cpFalse; + } + + return cpTrue; +} + +void +cpSpaceFilterArbiters(cpSpace *space, cpBody *body, cpShape *filter) +{ + cpSpaceLock(space); { + struct arbiterFilterContext context = {space, body, filter}; + cpHashSetFilter(space->cachedArbiters, (cpHashSetFilterFunc)cachedArbitersFilter, &context); + } cpSpaceUnlock(space, cpTrue); +} + +void +cpSpaceRemoveShape(cpSpace *space, cpShape *shape) +{ + cpBody *body = shape->body; + cpAssertHard(cpSpaceContainsShape(space, shape), "Cannot remove a shape that was not added to the space. (Removed twice maybe?)"); + cpAssertSpaceUnlocked(space); + + cpBool isStatic = (cpBodyGetType(body) == CP_BODY_TYPE_STATIC); + if(isStatic){ + cpBodyActivateStatic(body, shape); + } else { + cpBodyActivate(body); + } + + cpBodyRemoveShape(body, shape); + cpSpaceFilterArbiters(space, body, shape); + cpSpatialIndexRemove(isStatic ? space->staticShapes : space->dynamicShapes, shape, shape->hashid); + shape->space = NULL; + shape->hashid = 0; +} + +void +cpSpaceRemoveBody(cpSpace *space, cpBody *body) +{ + cpAssertHard(body != cpSpaceGetStaticBody(space), "Cannot remove the designated static body for the space."); + cpAssertHard(cpSpaceContainsBody(space, body), "Cannot remove a body that was not added to the space. (Removed twice maybe?)"); +// cpAssertHard(body->shapeList == NULL, "Cannot remove a body from the space before removing the bodies attached to it."); +// cpAssertHard(body->constraintList == NULL, "Cannot remove a body from the space before removing the constraints attached to it."); + cpAssertSpaceUnlocked(space); + + cpBodyActivate(body); +// cpSpaceFilterArbiters(space, body, NULL); + cpArrayDeleteObj(cpSpaceArrayForBodyType(space, cpBodyGetType(body)), body); + body->space = NULL; +} + +void +cpSpaceRemoveConstraint(cpSpace *space, cpConstraint *constraint) +{ + cpAssertHard(cpSpaceContainsConstraint(space, constraint), "Cannot remove a constraint that was not added to the space. (Removed twice maybe?)"); + cpAssertSpaceUnlocked(space); + + cpBodyActivate(constraint->a); + cpBodyActivate(constraint->b); + cpArrayDeleteObj(space->constraints, constraint); + + cpBodyRemoveConstraint(constraint->a, constraint); + cpBodyRemoveConstraint(constraint->b, constraint); + constraint->space = NULL; +} + +cpBool cpSpaceContainsShape(cpSpace *space, cpShape *shape) +{ + return (shape->space == space); +} + +cpBool cpSpaceContainsBody(cpSpace *space, cpBody *body) +{ + return (body->space == space); +} + +cpBool cpSpaceContainsConstraint(cpSpace *space, cpConstraint *constraint) +{ + return (constraint->space == space); +} + +//MARK: Iteration + +void +cpSpaceEachBody(cpSpace *space, cpSpaceBodyIteratorFunc func, void *data) +{ + cpSpaceLock(space); { + cpArray *bodies = space->dynamicBodies; + for(int i=0; inum; i++){ + func((cpBody *)bodies->arr[i], data); + } + + cpArray *otherBodies = space->staticBodies; + for(int i=0; inum; i++){ + func((cpBody *)otherBodies->arr[i], data); + } + + cpArray *components = space->sleepingComponents; + for(int i=0; inum; i++){ + cpBody *root = (cpBody *)components->arr[i]; + + cpBody *body = root; + while(body){ + cpBody *next = body->sleeping.next; + func(body, data); + body = next; + } + } + } cpSpaceUnlock(space, cpTrue); +} + +typedef struct spaceShapeContext { + cpSpaceShapeIteratorFunc func; + void *data; +} spaceShapeContext; + +static void +spaceEachShapeIterator(cpShape *shape, spaceShapeContext *context) +{ + context->func(shape, context->data); +} + +void +cpSpaceEachShape(cpSpace *space, cpSpaceShapeIteratorFunc func, void *data) +{ + cpSpaceLock(space); { + spaceShapeContext context = {func, data}; + cpSpatialIndexEach(space->dynamicShapes, (cpSpatialIndexIteratorFunc)spaceEachShapeIterator, &context); + cpSpatialIndexEach(space->staticShapes, (cpSpatialIndexIteratorFunc)spaceEachShapeIterator, &context); + } cpSpaceUnlock(space, cpTrue); +} + +void +cpSpaceEachConstraint(cpSpace *space, cpSpaceConstraintIteratorFunc func, void *data) +{ + cpSpaceLock(space); { + cpArray *constraints = space->constraints; + + for(int i=0; inum; i++){ + func((cpConstraint *)constraints->arr[i], data); + } + } cpSpaceUnlock(space, cpTrue); +} + +//MARK: Spatial Index Management + +void +cpSpaceReindexStatic(cpSpace *space) +{ + cpAssertHard(!space->locked, "You cannot manually reindex objects while the space is locked. Wait until the current query or step is complete."); + + cpSpatialIndexEach(space->staticShapes, (cpSpatialIndexIteratorFunc)&cpShapeUpdateFunc, NULL); + cpSpatialIndexReindex(space->staticShapes); +} + +void +cpSpaceReindexShape(cpSpace *space, cpShape *shape) +{ + cpAssertHard(!space->locked, "You cannot manually reindex objects while the space is locked. Wait until the current query or step is complete."); + + cpShapeCacheBB(shape); + + // attempt to rehash the shape in both hashes + cpSpatialIndexReindexObject(space->dynamicShapes, shape, shape->hashid); + cpSpatialIndexReindexObject(space->staticShapes, shape, shape->hashid); +} + +void +cpSpaceReindexShapesForBody(cpSpace *space, cpBody *body) +{ + CP_BODY_FOREACH_SHAPE(body, shape) cpSpaceReindexShape(space, shape); +} + + +static void +copyShapes(cpShape *shape, cpSpatialIndex *index) +{ + cpSpatialIndexInsert(index, shape, shape->hashid); +} + +void +cpSpaceUseSpatialHash(cpSpace *space, cpFloat dim, int count) +{ + cpSpatialIndex *staticShapes = cpSpaceHashNew(dim, count, (cpSpatialIndexBBFunc)cpShapeGetBB, NULL); + cpSpatialIndex *dynamicShapes = cpSpaceHashNew(dim, count, (cpSpatialIndexBBFunc)cpShapeGetBB, staticShapes); + + cpSpatialIndexEach(space->staticShapes, (cpSpatialIndexIteratorFunc)copyShapes, staticShapes); + cpSpatialIndexEach(space->dynamicShapes, (cpSpatialIndexIteratorFunc)copyShapes, dynamicShapes); + + cpSpatialIndexFree(space->staticShapes); + cpSpatialIndexFree(space->dynamicShapes); + + space->staticShapes = staticShapes; + space->dynamicShapes = dynamicShapes; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceComponent.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceComponent.c" new file mode 100644 index 0000000..7b2d606 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceComponent.c" @@ -0,0 +1,349 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * 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. + */ + +#include + +#include "chipmunk/chipmunk_private.h" + +//MARK: Sleeping Functions + +void +cpSpaceActivateBody(cpSpace *space, cpBody *body) +{ + cpAssertHard(cpBodyGetType(body) == CP_BODY_TYPE_DYNAMIC, "Internal error: Attempting to activate a non-dynamic body."); + + if(space->locked){ + // cpSpaceActivateBody() is called again once the space is unlocked + if(!cpArrayContains(space->rousedBodies, body)) cpArrayPush(space->rousedBodies, body); + } else { + cpAssertSoft(body->sleeping.root == NULL && body->sleeping.next == NULL, "Internal error: Activating body non-NULL node pointers."); + cpArrayPush(space->dynamicBodies, body); + + CP_BODY_FOREACH_SHAPE(body, shape){ + cpSpatialIndexRemove(space->staticShapes, shape, shape->hashid); + cpSpatialIndexInsert(space->dynamicShapes, shape, shape->hashid); + } + + CP_BODY_FOREACH_ARBITER(body, arb){ + cpBody *bodyA = arb->body_a; + + // Arbiters are shared between two bodies that are always woken up together. + // You only want to restore the arbiter once, so bodyA is arbitrarily chosen to own the arbiter. + // The edge case is when static bodies are involved as the static bodies never actually sleep. + // If the static body is bodyB then all is good. If the static body is bodyA, that can easily be checked. + if(body == bodyA || cpBodyGetType(bodyA) == CP_BODY_TYPE_STATIC){ + int numContacts = arb->count; + struct cpContact *contacts = arb->contacts; + + // Restore contact values back to the space's contact buffer memory + arb->contacts = cpContactBufferGetArray(space); + memcpy(arb->contacts, contacts, numContacts*sizeof(struct cpContact)); + cpSpacePushContacts(space, numContacts); + + // Reinsert the arbiter into the arbiter cache + const cpShape *a = arb->a, *b = arb->b; + const cpShape *shape_pair[] = {a, b}; + cpHashValue arbHashID = CP_HASH_PAIR((cpHashValue)a, (cpHashValue)b); + cpHashSetInsert(space->cachedArbiters, arbHashID, shape_pair, NULL, arb); + + // Update the arbiter's state + arb->stamp = space->stamp; + cpArrayPush(space->arbiters, arb); + + cpfree(contacts); + } + } + + CP_BODY_FOREACH_CONSTRAINT(body, constraint){ + cpBody *bodyA = constraint->a; + if(body == bodyA || cpBodyGetType(bodyA) == CP_BODY_TYPE_STATIC) cpArrayPush(space->constraints, constraint); + } + } +} + +static void +cpSpaceDeactivateBody(cpSpace *space, cpBody *body) +{ + cpAssertHard(cpBodyGetType(body) == CP_BODY_TYPE_DYNAMIC, "Internal error: Attempting to deactivate a non-dynamic body."); + + cpArrayDeleteObj(space->dynamicBodies, body); + + CP_BODY_FOREACH_SHAPE(body, shape){ + cpSpatialIndexRemove(space->dynamicShapes, shape, shape->hashid); + cpSpatialIndexInsert(space->staticShapes, shape, shape->hashid); + } + + CP_BODY_FOREACH_ARBITER(body, arb){ + cpBody *bodyA = arb->body_a; + if(body == bodyA || cpBodyGetType(bodyA) == CP_BODY_TYPE_STATIC){ + cpSpaceUncacheArbiter(space, arb); + + // Save contact values to a new block of memory so they won't time out + size_t bytes = arb->count*sizeof(struct cpContact); + struct cpContact *contacts = (struct cpContact *)cpcalloc(1, bytes); + memcpy(contacts, arb->contacts, bytes); + arb->contacts = contacts; + } + } + + CP_BODY_FOREACH_CONSTRAINT(body, constraint){ + cpBody *bodyA = constraint->a; + if(body == bodyA || cpBodyGetType(bodyA) == CP_BODY_TYPE_STATIC) cpArrayDeleteObj(space->constraints, constraint); + } +} + +static inline cpBody * +ComponentRoot(cpBody *body) +{ + return (body ? body->sleeping.root : NULL); +} + +void +cpBodyActivate(cpBody *body) +{ + if(body != NULL && cpBodyGetType(body) == CP_BODY_TYPE_DYNAMIC){ + body->sleeping.idleTime = 0.0f; + + cpBody *root = ComponentRoot(body); + if(root && cpBodyIsSleeping(root)){ + // TODO should cpBodyIsSleeping(root) be an assertion? + cpAssertSoft(cpBodyGetType(root) == CP_BODY_TYPE_DYNAMIC, "Internal Error: Non-dynamic body component root detected."); + + cpSpace *space = root->space; + cpBody *body = root; + while(body){ + cpBody *next = body->sleeping.next; + + body->sleeping.idleTime = 0.0f; + body->sleeping.root = NULL; + body->sleeping.next = NULL; + cpSpaceActivateBody(space, body); + + body = next; + } + + cpArrayDeleteObj(space->sleepingComponents, root); + } + + CP_BODY_FOREACH_ARBITER(body, arb){ + // Reset the idle timer of things the body is touching as well. + // That way things don't get left hanging in the air. + cpBody *other = (arb->body_a == body ? arb->body_b : arb->body_a); + if(cpBodyGetType(other) != CP_BODY_TYPE_STATIC) other->sleeping.idleTime = 0.0f; + } + } +} + +void +cpBodyActivateStatic(cpBody *body, cpShape *filter) +{ + cpAssertHard(cpBodyGetType(body) == CP_BODY_TYPE_STATIC, "cpBodyActivateStatic() called on a non-static body."); + + CP_BODY_FOREACH_ARBITER(body, arb){ + if(!filter || filter == arb->a || filter == arb->b){ + cpBodyActivate(arb->body_a == body ? arb->body_b : arb->body_a); + } + } + + // TODO: should also activate joints? +} + +static inline void +cpBodyPushArbiter(cpBody *body, cpArbiter *arb) +{ + cpAssertSoft(cpArbiterThreadForBody(arb, body)->next == NULL, "Internal Error: Dangling contact graph pointers detected. (A)"); + cpAssertSoft(cpArbiterThreadForBody(arb, body)->prev == NULL, "Internal Error: Dangling contact graph pointers detected. (B)"); + + cpArbiter *next = body->arbiterList; + cpAssertSoft(next == NULL || cpArbiterThreadForBody(next, body)->prev == NULL, "Internal Error: Dangling contact graph pointers detected. (C)"); + cpArbiterThreadForBody(arb, body)->next = next; + + if(next) cpArbiterThreadForBody(next, body)->prev = arb; + body->arbiterList = arb; +} + +static inline void +ComponentAdd(cpBody *root, cpBody *body){ + body->sleeping.root = root; + + if(body != root){ + body->sleeping.next = root->sleeping.next; + root->sleeping.next = body; + } +} + +static inline void +FloodFillComponent(cpBody *root, cpBody *body) +{ + // Kinematic bodies cannot be put to sleep and prevent bodies they are touching from sleeping. + // Static bodies are effectively sleeping all the time. + if(cpBodyGetType(body) == CP_BODY_TYPE_DYNAMIC){ + cpBody *other_root = ComponentRoot(body); + if(other_root == NULL){ + ComponentAdd(root, body); + CP_BODY_FOREACH_ARBITER(body, arb) FloodFillComponent(root, (body == arb->body_a ? arb->body_b : arb->body_a)); + CP_BODY_FOREACH_CONSTRAINT(body, constraint) FloodFillComponent(root, (body == constraint->a ? constraint->b : constraint->a)); + } else { + cpAssertSoft(other_root == root, "Internal Error: Inconsistency dectected in the contact graph."); + } + } +} + +static inline cpBool +ComponentActive(cpBody *root, cpFloat threshold) +{ + CP_BODY_FOREACH_COMPONENT(root, body){ + if(body->sleeping.idleTime < threshold) return cpTrue; + } + + return cpFalse; +} + +void +cpSpaceProcessComponents(cpSpace *space, cpFloat dt) +{ + cpBool sleep = (space->sleepTimeThreshold != INFINITY); + cpArray *bodies = space->dynamicBodies; + +#ifndef NDEBUG + for(int i=0; inum; i++){ + cpBody *body = (cpBody*)bodies->arr[i]; + + cpAssertSoft(body->sleeping.next == NULL, "Internal Error: Dangling next pointer detected in contact graph."); + cpAssertSoft(body->sleeping.root == NULL, "Internal Error: Dangling root pointer detected in contact graph."); + } +#endif + + // Calculate the kinetic energy of all the bodies. + if(sleep){ + cpFloat dv = space->idleSpeedThreshold; + cpFloat dvsq = (dv ? dv*dv : cpvlengthsq(space->gravity)*dt*dt); + + // update idling and reset component nodes + for(int i=0; inum; i++){ + cpBody *body = (cpBody*)bodies->arr[i]; + + // TODO should make a separate array for kinematic bodies. + if(cpBodyGetType(body) != CP_BODY_TYPE_DYNAMIC) continue; + + // Need to deal with infinite mass objects + cpFloat keThreshold = (dvsq ? body->m*dvsq : 0.0f); + body->sleeping.idleTime = (cpBodyKineticEnergy(body) > keThreshold ? 0.0f : body->sleeping.idleTime + dt); + } + } + + // Awaken any sleeping bodies found and then push arbiters to the bodies' lists. + cpArray *arbiters = space->arbiters; + for(int i=0, count=arbiters->num; iarr[i]; + cpBody *a = arb->body_a, *b = arb->body_b; + + if(sleep){ + // TODO checking cpBodyIsSleepin() redundant? + if(cpBodyGetType(b) == CP_BODY_TYPE_KINEMATIC || cpBodyIsSleeping(a)) cpBodyActivate(a); + if(cpBodyGetType(a) == CP_BODY_TYPE_KINEMATIC || cpBodyIsSleeping(b)) cpBodyActivate(b); + } + + cpBodyPushArbiter(a, arb); + cpBodyPushArbiter(b, arb); + } + + if(sleep){ + // Bodies should be held active if connected by a joint to a kinematic. + cpArray *constraints = space->constraints; + for(int i=0; inum; i++){ + cpConstraint *constraint = (cpConstraint *)constraints->arr[i]; + cpBody *a = constraint->a, *b = constraint->b; + + if(cpBodyGetType(b) == CP_BODY_TYPE_KINEMATIC) cpBodyActivate(a); + if(cpBodyGetType(a) == CP_BODY_TYPE_KINEMATIC) cpBodyActivate(b); + } + + // Generate components and deactivate sleeping ones + for(int i=0; inum;){ + cpBody *body = (cpBody*)bodies->arr[i]; + + if(ComponentRoot(body) == NULL){ + // Body not in a component yet. Perform a DFS to flood fill mark + // the component in the contact graph using this body as the root. + FloodFillComponent(body, body); + + // Check if the component should be put to sleep. + if(!ComponentActive(body, space->sleepTimeThreshold)){ + cpArrayPush(space->sleepingComponents, body); + CP_BODY_FOREACH_COMPONENT(body, other) cpSpaceDeactivateBody(space, other); + + // cpSpaceDeactivateBody() removed the current body from the list. + // Skip incrementing the index counter. + continue; + } + } + + i++; + + // Only sleeping bodies retain their component node pointers. + body->sleeping.root = NULL; + body->sleeping.next = NULL; + } + } +} + +void +cpBodySleep(cpBody *body) +{ + cpBodySleepWithGroup(body, NULL); +} + +void +cpBodySleepWithGroup(cpBody *body, cpBody *group){ + cpAssertHard(cpBodyGetType(body) == CP_BODY_TYPE_DYNAMIC, "Non-dynamic bodies cannot be put to sleep."); + + cpSpace *space = body->space; + cpAssertHard(!cpSpaceIsLocked(space), "Bodies cannot be put to sleep during a query or a call to cpSpaceStep(). Put these calls into a post-step callback."); + cpAssertHard(cpSpaceGetSleepTimeThreshold(space) < INFINITY, "Sleeping is not enabled on the space. You cannot sleep a body without setting a sleep time threshold on the space."); + cpAssertHard(group == NULL || cpBodyIsSleeping(group), "Cannot use a non-sleeping body as a group identifier."); + + if(cpBodyIsSleeping(body)){ + cpAssertHard(ComponentRoot(body) == ComponentRoot(group), "The body is already sleeping and it's group cannot be reassigned."); + return; + } + + CP_BODY_FOREACH_SHAPE(body, shape) cpShapeCacheBB(shape); + cpSpaceDeactivateBody(space, body); + + if(group){ + cpBody *root = ComponentRoot(group); + + body->sleeping.root = root; + body->sleeping.next = root->sleeping.next; + body->sleeping.idleTime = 0.0f; + + root->sleeping.next = body; + } else { + body->sleeping.root = body; + body->sleeping.next = NULL; + body->sleeping.idleTime = 0.0f; + + cpArrayPush(space->sleepingComponents, body); + } + + cpArrayDeleteObj(space->dynamicBodies, body); +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceDebug.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceDebug.c" new file mode 100644 index 0000000..4711ac5 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceDebug.c" @@ -0,0 +1,189 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +#ifndef CP_SPACE_DISABLE_DEBUG_API + +static void +cpSpaceDebugDrawShape(cpShape *shape, cpSpaceDebugDrawOptions *options) +{ + cpBody *body = shape->body; + cpDataPointer data = options->data; + + cpSpaceDebugColor outline_color = options->shapeOutlineColor; + cpSpaceDebugColor fill_color = options->colorForShape(shape, data); + + switch(shape->klass->type){ + case CP_CIRCLE_SHAPE: { + cpCircleShape *circle = (cpCircleShape *)shape; + options->drawCircle(circle->tc, body->a, circle->r, outline_color, fill_color, data); + break; + } + case CP_SEGMENT_SHAPE: { + cpSegmentShape *seg = (cpSegmentShape *)shape; + options->drawFatSegment(seg->ta, seg->tb, seg->r, outline_color, fill_color, data); + break; + } + case CP_POLY_SHAPE: { + cpPolyShape *poly = (cpPolyShape *)shape; + + int count = poly->count; + struct cpSplittingPlane *planes = poly->planes; + cpVect *verts = (cpVect *)alloca(count*sizeof(cpVect)); + + for(int i=0; idrawPolygon(count, verts, poly->r, outline_color, fill_color, data); + break; + } + default: break; + } +} + +static const cpVect spring_verts[] = { + {0.00f, 0.0f}, + {0.20f, 0.0f}, + {0.25f, 3.0f}, + {0.30f,-6.0f}, + {0.35f, 6.0f}, + {0.40f,-6.0f}, + {0.45f, 6.0f}, + {0.50f,-6.0f}, + {0.55f, 6.0f}, + {0.60f,-6.0f}, + {0.65f, 6.0f}, + {0.70f,-3.0f}, + {0.75f, 6.0f}, + {0.80f, 0.0f}, + {1.00f, 0.0f}, +}; +static const int spring_count = sizeof(spring_verts)/sizeof(cpVect); + +static void +cpSpaceDebugDrawConstraint(cpConstraint *constraint, cpSpaceDebugDrawOptions *options) +{ + cpDataPointer data = options->data; + cpSpaceDebugColor color = options->constraintColor; + + cpBody *body_a = constraint->a; + cpBody *body_b = constraint->b; + + if(cpConstraintIsPinJoint(constraint)){ + cpPinJoint *joint = (cpPinJoint *)constraint; + + cpVect a = cpTransformPoint(body_a->transform, joint->anchorA); + cpVect b = cpTransformPoint(body_b->transform, joint->anchorB); + + options->drawDot(5, a, color, data); + options->drawDot(5, b, color, data); + options->drawSegment(a, b, color, data); + } else if(cpConstraintIsSlideJoint(constraint)){ + cpSlideJoint *joint = (cpSlideJoint *)constraint; + + cpVect a = cpTransformPoint(body_a->transform, joint->anchorA); + cpVect b = cpTransformPoint(body_b->transform, joint->anchorB); + + options->drawDot(5, a, color, data); + options->drawDot(5, b, color, data); + options->drawSegment(a, b, color, data); + } else if(cpConstraintIsPivotJoint(constraint)){ + cpPivotJoint *joint = (cpPivotJoint *)constraint; + + cpVect a = cpTransformPoint(body_a->transform, joint->anchorA); + cpVect b = cpTransformPoint(body_b->transform, joint->anchorB); + + options->drawDot(5, a, color, data); + options->drawDot(5, b, color, data); + } else if(cpConstraintIsGrooveJoint(constraint)){ + cpGrooveJoint *joint = (cpGrooveJoint *)constraint; + + cpVect a = cpTransformPoint(body_a->transform, joint->grv_a); + cpVect b = cpTransformPoint(body_a->transform, joint->grv_b); + cpVect c = cpTransformPoint(body_b->transform, joint->anchorB); + + options->drawDot(5, c, color, data); + options->drawSegment(a, b, color, data); + } else if(cpConstraintIsDampedSpring(constraint)){ + cpDampedSpring *spring = (cpDampedSpring *)constraint; + cpDataPointer data = options->data; + cpSpaceDebugColor color = options->constraintColor; + + cpVect a = cpTransformPoint(body_a->transform, spring->anchorA); + cpVect b = cpTransformPoint(body_b->transform, spring->anchorB); + + options->drawDot(5, a, color, data); + options->drawDot(5, b, color, data); + + cpVect delta = cpvsub(b, a); + cpFloat cos = delta.x; + cpFloat sin = delta.y; + cpFloat s = 1.0f/cpvlength(delta); + + cpVect r1 = cpv(cos, -sin*s); + cpVect r2 = cpv(sin, cos*s); + + cpVect *verts = (cpVect *)alloca(spring_count*sizeof(cpVect)); + for(int i=0; idrawSegment(verts[i], verts[i + 1], color, data); + } + } +} + +void +cpSpaceDebugDraw(cpSpace *space, cpSpaceDebugDrawOptions *options) +{ + if(options->flags & CP_SPACE_DEBUG_DRAW_SHAPES){ + cpSpaceEachShape(space, (cpSpaceShapeIteratorFunc)cpSpaceDebugDrawShape, options); + } + + if(options->flags & CP_SPACE_DEBUG_DRAW_CONSTRAINTS){ + cpSpaceEachConstraint(space, (cpSpaceConstraintIteratorFunc)cpSpaceDebugDrawConstraint, options); + } + + if(options->flags & CP_SPACE_DEBUG_DRAW_COLLISION_POINTS){ + cpArray *arbiters = space->arbiters; + cpSpaceDebugColor color = options->collisionPointColor; + cpSpaceDebugDrawSegmentImpl draw_seg = options->drawSegment; + cpDataPointer data = options->data; + + for(int i=0; inum; i++){ + cpArbiter *arb = (cpArbiter*)arbiters->arr[i]; + cpVect n = arb->n; + + for(int j=0; jcount; j++){ + cpVect p1 = cpvadd(arb->body_a->p, arb->contacts[j].r1); + cpVect p2 = cpvadd(arb->body_b->p, arb->contacts[j].r2); + + cpFloat d = 2.0f; + cpVect a = cpvadd(p1, cpvmult(n, -d)); + cpVect b = cpvadd(p2, cpvmult(n, d)); + draw_seg(a, b, color, data); + } + } + } +} + +#endif diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceHash.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceHash.c" new file mode 100644 index 0000000..656c3bd --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceHash.c" @@ -0,0 +1,634 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" +#include "prime.h" + +typedef struct cpSpaceHashBin cpSpaceHashBin; +typedef struct cpHandle cpHandle; + +struct cpSpaceHash { + cpSpatialIndex spatialIndex; + + int numcells; + cpFloat celldim; + + cpSpaceHashBin **table; + cpHashSet *handleSet; + + cpSpaceHashBin *pooledBins; + cpArray *pooledHandles; + cpArray *allocatedBuffers; + + cpTimestamp stamp; +}; + + +//MARK: Handle Functions + +struct cpHandle { + void *obj; + int retain; + cpTimestamp stamp; +}; + +static cpHandle* +cpHandleInit(cpHandle *hand, void *obj) +{ + hand->obj = obj; + hand->retain = 0; + hand->stamp = 0; + + return hand; +} + +static inline void cpHandleRetain(cpHandle *hand){hand->retain++;} + +static inline void +cpHandleRelease(cpHandle *hand, cpArray *pooledHandles) +{ + hand->retain--; + if(hand->retain == 0) cpArrayPush(pooledHandles, hand); +} + +static int handleSetEql(void *obj, cpHandle *hand){return (obj == hand->obj);} + +static void * +handleSetTrans(void *obj, cpSpaceHash *hash) +{ + if(hash->pooledHandles->num == 0){ + // handle pool is exhausted, make more + int count = CP_BUFFER_BYTES/sizeof(cpHandle); + cpAssertHard(count, "Internal Error: Buffer size is too small."); + + cpHandle *buffer = (cpHandle *)cpcalloc(1, CP_BUFFER_BYTES); + cpArrayPush(hash->allocatedBuffers, buffer); + + for(int i=0; ipooledHandles, buffer + i); + } + + cpHandle *hand = cpHandleInit((cpHandle *)cpArrayPop(hash->pooledHandles), obj); + cpHandleRetain(hand); + + return hand; +} + +//MARK: Bin Functions + +struct cpSpaceHashBin { + cpHandle *handle; + cpSpaceHashBin *next; +}; + +static inline void +recycleBin(cpSpaceHash *hash, cpSpaceHashBin *bin) +{ + bin->next = hash->pooledBins; + hash->pooledBins = bin; +} + +static inline void +clearTableCell(cpSpaceHash *hash, int idx) +{ + cpSpaceHashBin *bin = hash->table[idx]; + while(bin){ + cpSpaceHashBin *next = bin->next; + + cpHandleRelease(bin->handle, hash->pooledHandles); + recycleBin(hash, bin); + + bin = next; + } + + hash->table[idx] = NULL; +} + +static void +clearTable(cpSpaceHash *hash) +{ + for(int i=0; inumcells; i++) clearTableCell(hash, i); +} + +// Get a recycled or new bin. +static inline cpSpaceHashBin * +getEmptyBin(cpSpaceHash *hash) +{ + cpSpaceHashBin *bin = hash->pooledBins; + + if(bin){ + hash->pooledBins = bin->next; + return bin; + } else { + // Pool is exhausted, make more + int count = CP_BUFFER_BYTES/sizeof(cpSpaceHashBin); + cpAssertHard(count, "Internal Error: Buffer size is too small."); + + cpSpaceHashBin *buffer = (cpSpaceHashBin *)cpcalloc(1, CP_BUFFER_BYTES); + cpArrayPush(hash->allocatedBuffers, buffer); + + // push all but the first one, return the first instead + for(int i=1; itable); + + hash->numcells = numcells; + hash->table = (cpSpaceHashBin **)cpcalloc(numcells, sizeof(cpSpaceHashBin *)); +} + +static inline cpSpatialIndexClass *Klass(); + +cpSpatialIndex * +cpSpaceHashInit(cpSpaceHash *hash, cpFloat celldim, int numcells, cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex) +{ + cpSpatialIndexInit((cpSpatialIndex *)hash, Klass(), bbfunc, staticIndex); + + cpSpaceHashAllocTable(hash, next_prime(numcells)); + hash->celldim = celldim; + + hash->handleSet = cpHashSetNew(0, (cpHashSetEqlFunc)handleSetEql); + + hash->pooledHandles = cpArrayNew(0); + + hash->pooledBins = NULL; + hash->allocatedBuffers = cpArrayNew(0); + + hash->stamp = 1; + + return (cpSpatialIndex *)hash; +} + +cpSpatialIndex * +cpSpaceHashNew(cpFloat celldim, int cells, cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex) +{ + return cpSpaceHashInit(cpSpaceHashAlloc(), celldim, cells, bbfunc, staticIndex); +} + +static void +cpSpaceHashDestroy(cpSpaceHash *hash) +{ + if(hash->table) clearTable(hash); + cpfree(hash->table); + + cpHashSetFree(hash->handleSet); + + cpArrayFreeEach(hash->allocatedBuffers, cpfree); + cpArrayFree(hash->allocatedBuffers); + cpArrayFree(hash->pooledHandles); +} + +//MARK: Helper Functions + +static inline cpBool +containsHandle(cpSpaceHashBin *bin, cpHandle *hand) +{ + while(bin){ + if(bin->handle == hand) return cpTrue; + bin = bin->next; + } + + return cpFalse; +} + +// The hash function itself. +static inline cpHashValue +hash_func(cpHashValue x, cpHashValue y, cpHashValue n) +{ + return (x*1640531513ul ^ y*2654435789ul) % n; +} + +// Much faster than (int)floor(f) +// Profiling showed floor() to be a sizable performance hog +static inline int +floor_int(cpFloat f) +{ + int i = (int)f; + return (f < 0.0f && f != i ? i - 1 : i); +} + +static inline void +hashHandle(cpSpaceHash *hash, cpHandle *hand, cpBB bb) +{ + // Find the dimensions in cell coordinates. + cpFloat dim = hash->celldim; + int l = floor_int(bb.l/dim); // Fix by ShiftZ + int r = floor_int(bb.r/dim); + int b = floor_int(bb.b/dim); + int t = floor_int(bb.t/dim); + + int n = hash->numcells; + for(int i=l; i<=r; i++){ + for(int j=b; j<=t; j++){ + cpHashValue idx = hash_func(i,j,n); + cpSpaceHashBin *bin = hash->table[idx]; + + // Don't add an object twice to the same cell. + if(containsHandle(bin, hand)) continue; + + cpHandleRetain(hand); + // Insert a new bin for the handle in this cell. + cpSpaceHashBin *newBin = getEmptyBin(hash); + newBin->handle = hand; + newBin->next = bin; + hash->table[idx] = newBin; + } + } +} + +//MARK: Basic Operations + +static void +cpSpaceHashInsert(cpSpaceHash *hash, void *obj, cpHashValue hashid) +{ + cpHandle *hand = (cpHandle *)cpHashSetInsert(hash->handleSet, hashid, obj, (cpHashSetTransFunc)handleSetTrans, hash); + hashHandle(hash, hand, hash->spatialIndex.bbfunc(obj)); +} + +static void +cpSpaceHashRehashObject(cpSpaceHash *hash, void *obj, cpHashValue hashid) +{ + cpHandle *hand = (cpHandle *)cpHashSetRemove(hash->handleSet, hashid, obj); + + if(hand){ + hand->obj = NULL; + cpHandleRelease(hand, hash->pooledHandles); + + cpSpaceHashInsert(hash, obj, hashid); + } +} + +static void +rehash_helper(cpHandle *hand, cpSpaceHash *hash) +{ + hashHandle(hash, hand, hash->spatialIndex.bbfunc(hand->obj)); +} + +static void +cpSpaceHashRehash(cpSpaceHash *hash) +{ + clearTable(hash); + cpHashSetEach(hash->handleSet, (cpHashSetIteratorFunc)rehash_helper, hash); +} + +static void +cpSpaceHashRemove(cpSpaceHash *hash, void *obj, cpHashValue hashid) +{ + cpHandle *hand = (cpHandle *)cpHashSetRemove(hash->handleSet, hashid, obj); + + if(hand){ + hand->obj = NULL; + cpHandleRelease(hand, hash->pooledHandles); + } +} + +typedef struct eachContext { + cpSpatialIndexIteratorFunc func; + void *data; +} eachContext; + +static void eachHelper(cpHandle *hand, eachContext *context){context->func(hand->obj, context->data);} + +static void +cpSpaceHashEach(cpSpaceHash *hash, cpSpatialIndexIteratorFunc func, void *data) +{ + eachContext context = {func, data}; + cpHashSetEach(hash->handleSet, (cpHashSetIteratorFunc)eachHelper, &context); +} + +static void +remove_orphaned_handles(cpSpaceHash *hash, cpSpaceHashBin **bin_ptr) +{ + cpSpaceHashBin *bin = *bin_ptr; + while(bin){ + cpHandle *hand = bin->handle; + cpSpaceHashBin *next = bin->next; + + if(!hand->obj){ + // orphaned handle, unlink and recycle the bin + (*bin_ptr) = bin->next; + recycleBin(hash, bin); + + cpHandleRelease(hand, hash->pooledHandles); + } else { + bin_ptr = &bin->next; + } + + bin = next; + } +} + +//MARK: Query Functions + +static inline void +query_helper(cpSpaceHash *hash, cpSpaceHashBin **bin_ptr, void *obj, cpSpatialIndexQueryFunc func, void *data) +{ + restart: + for(cpSpaceHashBin *bin = *bin_ptr; bin; bin = bin->next){ + cpHandle *hand = bin->handle; + void *other = hand->obj; + + if(hand->stamp == hash->stamp || obj == other){ + continue; + } else if(other){ + func(obj, other, 0, data); + hand->stamp = hash->stamp; + } else { + // The object for this handle has been removed + // cleanup this cell and restart the query + remove_orphaned_handles(hash, bin_ptr); + goto restart; // GCC not smart enough/able to tail call an inlined function. + } + } +} + +static void +cpSpaceHashQuery(cpSpaceHash *hash, void *obj, cpBB bb, cpSpatialIndexQueryFunc func, void *data) +{ + // Get the dimensions in cell coordinates. + cpFloat dim = hash->celldim; + int l = floor_int(bb.l/dim); // Fix by ShiftZ + int r = floor_int(bb.r/dim); + int b = floor_int(bb.b/dim); + int t = floor_int(bb.t/dim); + + int n = hash->numcells; + cpSpaceHashBin **table = hash->table; + + // Iterate over the cells and query them. + for(int i=l; i<=r; i++){ + for(int j=b; j<=t; j++){ + query_helper(hash, &table[hash_func(i,j,n)], obj, func, data); + } + } + + hash->stamp++; +} + +// Similar to struct eachPair above. +typedef struct queryRehashContext { + cpSpaceHash *hash; + cpSpatialIndexQueryFunc func; + void *data; +} queryRehashContext; + +// Hashset iterator func used with cpSpaceHashQueryRehash(). +static void +queryRehash_helper(cpHandle *hand, queryRehashContext *context) +{ + cpSpaceHash *hash = context->hash; + cpSpatialIndexQueryFunc func = context->func; + void *data = context->data; + + cpFloat dim = hash->celldim; + int n = hash->numcells; + + void *obj = hand->obj; + cpBB bb = hash->spatialIndex.bbfunc(obj); + + int l = floor_int(bb.l/dim); + int r = floor_int(bb.r/dim); + int b = floor_int(bb.b/dim); + int t = floor_int(bb.t/dim); + + cpSpaceHashBin **table = hash->table; + + for(int i=l; i<=r; i++){ + for(int j=b; j<=t; j++){ + cpHashValue idx = hash_func(i,j,n); + cpSpaceHashBin *bin = table[idx]; + + if(containsHandle(bin, hand)) continue; + + cpHandleRetain(hand); // this MUST be done first in case the object is removed in func() + query_helper(hash, &bin, obj, func, data); + + cpSpaceHashBin *newBin = getEmptyBin(hash); + newBin->handle = hand; + newBin->next = bin; + table[idx] = newBin; + } + } + + // Increment the stamp for each object hashed. + hash->stamp++; +} + +static void +cpSpaceHashReindexQuery(cpSpaceHash *hash, cpSpatialIndexQueryFunc func, void *data) +{ + clearTable(hash); + + queryRehashContext context = {hash, func, data}; + cpHashSetEach(hash->handleSet, (cpHashSetIteratorFunc)queryRehash_helper, &context); + + cpSpatialIndexCollideStatic((cpSpatialIndex *)hash, hash->spatialIndex.staticIndex, func, data); +} + +static inline cpFloat +segmentQuery_helper(cpSpaceHash *hash, cpSpaceHashBin **bin_ptr, void *obj, cpSpatialIndexSegmentQueryFunc func, void *data) +{ + cpFloat t = 1.0f; + + restart: + for(cpSpaceHashBin *bin = *bin_ptr; bin; bin = bin->next){ + cpHandle *hand = bin->handle; + void *other = hand->obj; + + // Skip over certain conditions + if(hand->stamp == hash->stamp){ + continue; + } else if(other){ + t = cpfmin(t, func(obj, other, data)); + hand->stamp = hash->stamp; + } else { + // The object for this handle has been removed + // cleanup this cell and restart the query + remove_orphaned_handles(hash, bin_ptr); + goto restart; // GCC not smart enough/able to tail call an inlined function. + } + } + + return t; +} + +// modified from http://playtechs.blogspot.com/2007/03/raytracing-on-grid.html +static void +cpSpaceHashSegmentQuery(cpSpaceHash *hash, void *obj, cpVect a, cpVect b, cpFloat t_exit, cpSpatialIndexSegmentQueryFunc func, void *data) +{ + a = cpvmult(a, 1.0f/hash->celldim); + b = cpvmult(b, 1.0f/hash->celldim); + + int cell_x = floor_int(a.x), cell_y = floor_int(a.y); + + cpFloat t = 0; + + int x_inc, y_inc; + cpFloat temp_v, temp_h; + + if (b.x > a.x){ + x_inc = 1; + temp_h = (cpffloor(a.x + 1.0f) - a.x); + } else { + x_inc = -1; + temp_h = (a.x - cpffloor(a.x)); + } + + if (b.y > a.y){ + y_inc = 1; + temp_v = (cpffloor(a.y + 1.0f) - a.y); + } else { + y_inc = -1; + temp_v = (a.y - cpffloor(a.y)); + } + + // Division by zero is *very* slow on ARM + cpFloat dx = cpfabs(b.x - a.x), dy = cpfabs(b.y - a.y); + cpFloat dt_dx = (dx ? 1.0f/dx : INFINITY), dt_dy = (dy ? 1.0f/dy : INFINITY); + + // fix NANs in horizontal directions + cpFloat next_h = (temp_h ? temp_h*dt_dx : dt_dx); + cpFloat next_v = (temp_v ? temp_v*dt_dy : dt_dy); + + int n = hash->numcells; + cpSpaceHashBin **table = hash->table; + + while(t < t_exit){ + cpHashValue idx = hash_func(cell_x, cell_y, n); + t_exit = cpfmin(t_exit, segmentQuery_helper(hash, &table[idx], obj, func, data)); + + if (next_v < next_h){ + cell_y += y_inc; + t = next_v; + next_v += dt_dy; + } else { + cell_x += x_inc; + t = next_h; + next_h += dt_dx; + } + } + + hash->stamp++; +} + +//MARK: Misc + +void +cpSpaceHashResize(cpSpaceHash *hash, cpFloat celldim, int numcells) +{ + if(hash->spatialIndex.klass != Klass()){ + cpAssertWarn(cpFalse, "Ignoring cpSpaceHashResize() call to non-cpSpaceHash spatial index."); + return; + } + + clearTable(hash); + + hash->celldim = celldim; + cpSpaceHashAllocTable(hash, next_prime(numcells)); +} + +static int +cpSpaceHashCount(cpSpaceHash *hash) +{ + return cpHashSetCount(hash->handleSet); +} + +static int +cpSpaceHashContains(cpSpaceHash *hash, void *obj, cpHashValue hashid) +{ + return cpHashSetFind(hash->handleSet, hashid, obj) != NULL; +} + +static cpSpatialIndexClass klass = { + (cpSpatialIndexDestroyImpl)cpSpaceHashDestroy, + + (cpSpatialIndexCountImpl)cpSpaceHashCount, + (cpSpatialIndexEachImpl)cpSpaceHashEach, + (cpSpatialIndexContainsImpl)cpSpaceHashContains, + + (cpSpatialIndexInsertImpl)cpSpaceHashInsert, + (cpSpatialIndexRemoveImpl)cpSpaceHashRemove, + + (cpSpatialIndexReindexImpl)cpSpaceHashRehash, + (cpSpatialIndexReindexObjectImpl)cpSpaceHashRehashObject, + (cpSpatialIndexReindexQueryImpl)cpSpaceHashReindexQuery, + + (cpSpatialIndexQueryImpl)cpSpaceHashQuery, + (cpSpatialIndexSegmentQueryImpl)cpSpaceHashSegmentQuery, +}; + +static inline cpSpatialIndexClass *Klass(){return &klass;} + +//MARK: Debug Drawing + +//#define CP_BBTREE_DEBUG_DRAW +#ifdef CP_BBTREE_DEBUG_DRAW +#include "OpenGL/gl.h" +#include "OpenGL/glu.h" +#include + +void +cpSpaceHashRenderDebug(cpSpatialIndex *index) +{ + if(index->klass != &klass){ + cpAssertWarn(cpFalse, "Ignoring cpSpaceHashRenderDebug() call to non-spatial hash spatial index."); + return; + } + + cpSpaceHash *hash = (cpSpaceHash *)index; + cpBB bb = cpBBNew(-320, -240, 320, 240); + + cpFloat dim = hash->celldim; + int n = hash->numcells; + + int l = (int)floor(bb.l/dim); + int r = (int)floor(bb.r/dim); + int b = (int)floor(bb.b/dim); + int t = (int)floor(bb.t/dim); + + for(int i=l; i<=r; i++){ + for(int j=b; j<=t; j++){ + int cell_count = 0; + + int index = hash_func(i,j,n); + for(cpSpaceHashBin *bin = hash->table[index]; bin; bin = bin->next) + cell_count++; + + GLfloat v = 1.0f - (GLfloat)cell_count/10.0f; + glColor3f(v,v,v); + glRectf(i*dim, j*dim, (i + 1)*dim, (j + 1)*dim); + } + } +} +#endif diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceQuery.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceQuery.c" new file mode 100644 index 0000000..1ce4a10 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceQuery.c" @@ -0,0 +1,246 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +//MARK: Nearest Point Query Functions + +struct PointQueryContext { + cpVect point; + cpFloat maxDistance; + cpShapeFilter filter; + cpSpacePointQueryFunc func; +}; + +static cpCollisionID +NearestPointQuery(struct PointQueryContext *context, cpShape *shape, cpCollisionID id, void *data) +{ + if( + !cpShapeFilterReject(shape->filter, context->filter) + ){ + cpPointQueryInfo info; + cpShapePointQuery(shape, context->point, &info); + + if(info.shape && info.distance < context->maxDistance) context->func(shape, info.point, info.distance, info.gradient, data); + } + + return id; +} + +void +cpSpacePointQuery(cpSpace *space, cpVect point, cpFloat maxDistance, cpShapeFilter filter, cpSpacePointQueryFunc func, void *data) +{ + struct PointQueryContext context = {point, maxDistance, filter, func}; + cpBB bb = cpBBNewForCircle(point, cpfmax(maxDistance, 0.0f)); + + cpSpaceLock(space); { + cpSpatialIndexQuery(space->dynamicShapes, &context, bb, (cpSpatialIndexQueryFunc)NearestPointQuery, data); + cpSpatialIndexQuery(space->staticShapes, &context, bb, (cpSpatialIndexQueryFunc)NearestPointQuery, data); + } cpSpaceUnlock(space, cpTrue); +} + +static cpCollisionID +NearestPointQueryNearest(struct PointQueryContext *context, cpShape *shape, cpCollisionID id, cpPointQueryInfo *out) +{ + if( + !cpShapeFilterReject(shape->filter, context->filter) && !shape->sensor + ){ + cpPointQueryInfo info; + cpShapePointQuery(shape, context->point, &info); + + if(info.distance < out->distance) (*out) = info; + } + + return id; +} + +cpShape * +cpSpacePointQueryNearest(cpSpace *space, cpVect point, cpFloat maxDistance, cpShapeFilter filter, cpPointQueryInfo *out) +{ + cpPointQueryInfo info = {NULL, cpvzero, maxDistance, cpvzero}; + if(out){ + (*out) = info; + } else { + out = &info; + } + + struct PointQueryContext context = { + point, maxDistance, + filter, + NULL + }; + + cpBB bb = cpBBNewForCircle(point, cpfmax(maxDistance, 0.0f)); + cpSpatialIndexQuery(space->dynamicShapes, &context, bb, (cpSpatialIndexQueryFunc)NearestPointQueryNearest, out); + cpSpatialIndexQuery(space->staticShapes, &context, bb, (cpSpatialIndexQueryFunc)NearestPointQueryNearest, out); + + return (cpShape *)out->shape; +} + + +//MARK: Segment Query Functions + +struct SegmentQueryContext { + cpVect start, end; + cpFloat radius; + cpShapeFilter filter; + cpSpaceSegmentQueryFunc func; +}; + +static cpFloat +SegmentQuery(struct SegmentQueryContext *context, cpShape *shape, void *data) +{ + cpSegmentQueryInfo info; + + if( + !cpShapeFilterReject(shape->filter, context->filter) && + cpShapeSegmentQuery(shape, context->start, context->end, context->radius, &info) + ){ + context->func(shape, info.point, info.normal, info.alpha, data); + } + + return 1.0f; +} + +void +cpSpaceSegmentQuery(cpSpace *space, cpVect start, cpVect end, cpFloat radius, cpShapeFilter filter, cpSpaceSegmentQueryFunc func, void *data) +{ + struct SegmentQueryContext context = { + start, end, + radius, + filter, + func, + }; + + cpSpaceLock(space); { + cpSpatialIndexSegmentQuery(space->staticShapes, &context, start, end, 1.0f, (cpSpatialIndexSegmentQueryFunc)SegmentQuery, data); + cpSpatialIndexSegmentQuery(space->dynamicShapes, &context, start, end, 1.0f, (cpSpatialIndexSegmentQueryFunc)SegmentQuery, data); + } cpSpaceUnlock(space, cpTrue); +} + +static cpFloat +SegmentQueryFirst(struct SegmentQueryContext *context, cpShape *shape, cpSegmentQueryInfo *out) +{ + cpSegmentQueryInfo info; + + if( + !cpShapeFilterReject(shape->filter, context->filter) && !shape->sensor && + cpShapeSegmentQuery(shape, context->start, context->end, context->radius, &info) && + info.alpha < out->alpha + ){ + (*out) = info; + } + + return out->alpha; +} + +cpShape * +cpSpaceSegmentQueryFirst(cpSpace *space, cpVect start, cpVect end, cpFloat radius, cpShapeFilter filter, cpSegmentQueryInfo *out) +{ + cpSegmentQueryInfo info = {NULL, end, cpvzero, 1.0f}; + if(out){ + (*out) = info; + } else { + out = &info; + } + + struct SegmentQueryContext context = { + start, end, + radius, + filter, + NULL + }; + + cpSpatialIndexSegmentQuery(space->staticShapes, &context, start, end, 1.0f, (cpSpatialIndexSegmentQueryFunc)SegmentQueryFirst, out); + cpSpatialIndexSegmentQuery(space->dynamicShapes, &context, start, end, out->alpha, (cpSpatialIndexSegmentQueryFunc)SegmentQueryFirst, out); + + return (cpShape *)out->shape; +} + +//MARK: BB Query Functions + +struct BBQueryContext { + cpBB bb; + cpShapeFilter filter; + cpSpaceBBQueryFunc func; +}; + +static cpCollisionID +BBQuery(struct BBQueryContext *context, cpShape *shape, cpCollisionID id, void *data) +{ + if( + !cpShapeFilterReject(shape->filter, context->filter) && + cpBBIntersects(context->bb, shape->bb) + ){ + context->func(shape, data); + } + + return id; +} + +void +cpSpaceBBQuery(cpSpace *space, cpBB bb, cpShapeFilter filter, cpSpaceBBQueryFunc func, void *data) +{ + struct BBQueryContext context = {bb, filter, func}; + + cpSpaceLock(space); { + cpSpatialIndexQuery(space->dynamicShapes, &context, bb, (cpSpatialIndexQueryFunc)BBQuery, data); + cpSpatialIndexQuery(space->staticShapes, &context, bb, (cpSpatialIndexQueryFunc)BBQuery, data); + } cpSpaceUnlock(space, cpTrue); +} + +//MARK: Shape Query Functions + +struct ShapeQueryContext { + cpSpaceShapeQueryFunc func; + void *data; + cpBool anyCollision; +}; + +// Callback from the spatial hash. +static cpCollisionID +ShapeQuery(cpShape *a, cpShape *b, cpCollisionID id, struct ShapeQueryContext *context) +{ + if(cpShapeFilterReject(a->filter, b->filter) || a == b) return id; + + cpContactPointSet set = cpShapesCollide(a, b); + if(set.count){ + if(context->func) context->func(b, &set, context->data); + context->anyCollision = !(a->sensor || b->sensor); + } + + return id; +} + +cpBool +cpSpaceShapeQuery(cpSpace *space, cpShape *shape, cpSpaceShapeQueryFunc func, void *data) +{ + cpBody *body = shape->body; + cpBB bb = (body ? cpShapeUpdate(shape, body->transform) : shape->bb); + struct ShapeQueryContext context = {func, data, cpFalse}; + + cpSpaceLock(space); { + cpSpatialIndexQuery(space->dynamicShapes, shape, bb, (cpSpatialIndexQueryFunc)ShapeQuery, &context); + cpSpatialIndexQuery(space->staticShapes, shape, bb, (cpSpatialIndexQueryFunc)ShapeQuery, &context); + } cpSpaceUnlock(space, cpTrue); + + return context.anyCollision; +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceStep.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceStep.c" new file mode 100644 index 0000000..85cbb3d --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceStep.c" @@ -0,0 +1,445 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +//MARK: Post Step Callback Functions + +cpPostStepCallback * +cpSpaceGetPostStepCallback(cpSpace *space, void *key) +{ + cpArray *arr = space->postStepCallbacks; + for(int i=0; inum; i++){ + cpPostStepCallback *callback = (cpPostStepCallback *)arr->arr[i]; + if(callback && callback->key == key) return callback; + } + + return NULL; +} + +static void PostStepDoNothing(cpSpace *space, void *obj, void *data){} + +cpBool +cpSpaceAddPostStepCallback(cpSpace *space, cpPostStepFunc func, void *key, void *data) +{ + cpAssertWarn(space->locked, + "Adding a post-step callback when the space is not locked is unnecessary. " + "Post-step callbacks will not called until the end of the next call to cpSpaceStep() or the next query."); + + if(!cpSpaceGetPostStepCallback(space, key)){ + cpPostStepCallback *callback = (cpPostStepCallback *)cpcalloc(1, sizeof(cpPostStepCallback)); + callback->func = (func ? func : PostStepDoNothing); + callback->key = key; + callback->data = data; + + cpArrayPush(space->postStepCallbacks, callback); + return cpTrue; + } else { + return cpFalse; + } +} + +//MARK: Locking Functions + +void +cpSpaceLock(cpSpace *space) +{ + space->locked++; +} + +void +cpSpaceUnlock(cpSpace *space, cpBool runPostStep) +{ + space->locked--; + cpAssertHard(space->locked >= 0, "Internal Error: Space lock underflow."); + + if(space->locked == 0){ + cpArray *waking = space->rousedBodies; + + for(int i=0, count=waking->num; iarr[i]); + waking->arr[i] = NULL; + } + + waking->num = 0; + + if(space->locked == 0 && runPostStep && !space->skipPostStep){ + space->skipPostStep = cpTrue; + + cpArray *arr = space->postStepCallbacks; + for(int i=0; inum; i++){ + cpPostStepCallback *callback = (cpPostStepCallback *)arr->arr[i]; + cpPostStepFunc func = callback->func; + + // Mark the func as NULL in case calling it calls cpSpaceRunPostStepCallbacks() again. + // TODO: need more tests around this case I think. + callback->func = NULL; + if(func) func(space, callback->key, callback->data); + + arr->arr[i] = NULL; + cpfree(callback); + } + + arr->num = 0; + space->skipPostStep = cpFalse; + } + } +} + +//MARK: Contact Buffer Functions + +struct cpContactBufferHeader { + cpTimestamp stamp; + cpContactBufferHeader *next; + unsigned int numContacts; +}; + +#define CP_CONTACTS_BUFFER_SIZE ((CP_BUFFER_BYTES - sizeof(cpContactBufferHeader))/sizeof(struct cpContact)) +typedef struct cpContactBuffer { + cpContactBufferHeader header; + struct cpContact contacts[CP_CONTACTS_BUFFER_SIZE]; +} cpContactBuffer; + +static cpContactBufferHeader * +cpSpaceAllocContactBuffer(cpSpace *space) +{ + cpContactBuffer *buffer = (cpContactBuffer *)cpcalloc(1, sizeof(cpContactBuffer)); + cpArrayPush(space->allocatedBuffers, buffer); + return (cpContactBufferHeader *)buffer; +} + +static cpContactBufferHeader * +cpContactBufferHeaderInit(cpContactBufferHeader *header, cpTimestamp stamp, cpContactBufferHeader *splice) +{ + header->stamp = stamp; + header->next = (splice ? splice->next : header); + header->numContacts = 0; + + return header; +} + +void +cpSpacePushFreshContactBuffer(cpSpace *space) +{ + cpTimestamp stamp = space->stamp; + + cpContactBufferHeader *head = space->contactBuffersHead; + + if(!head){ + // No buffers have been allocated, make one + space->contactBuffersHead = cpContactBufferHeaderInit(cpSpaceAllocContactBuffer(space), stamp, NULL); + } else if(stamp - head->next->stamp > space->collisionPersistence){ + // The tail buffer is available, rotate the ring + cpContactBufferHeader *tail = head->next; + space->contactBuffersHead = cpContactBufferHeaderInit(tail, stamp, tail); + } else { + // Allocate a new buffer and push it into the ring + cpContactBufferHeader *buffer = cpContactBufferHeaderInit(cpSpaceAllocContactBuffer(space), stamp, head); + space->contactBuffersHead = head->next = buffer; + } +} + + +struct cpContact * +cpContactBufferGetArray(cpSpace *space) +{ + if(space->contactBuffersHead->numContacts + CP_MAX_CONTACTS_PER_ARBITER > CP_CONTACTS_BUFFER_SIZE){ + // contact buffer could overflow on the next collision, push a fresh one. + cpSpacePushFreshContactBuffer(space); + } + + cpContactBufferHeader *head = space->contactBuffersHead; + return ((cpContactBuffer *)head)->contacts + head->numContacts; +} + +void +cpSpacePushContacts(cpSpace *space, int count) +{ + cpAssertHard(count <= CP_MAX_CONTACTS_PER_ARBITER, "Internal Error: Contact buffer overflow!"); + space->contactBuffersHead->numContacts += count; +} + +static void +cpSpacePopContacts(cpSpace *space, int count){ + space->contactBuffersHead->numContacts -= count; +} + +//MARK: Collision Detection Functions + +static void * +cpSpaceArbiterSetTrans(cpShape **shapes, cpSpace *space) +{ + if(space->pooledArbiters->num == 0){ + // arbiter pool is exhausted, make more + int count = CP_BUFFER_BYTES/sizeof(cpArbiter); + cpAssertHard(count, "Internal Error: Buffer size too small."); + + cpArbiter *buffer = (cpArbiter *)cpcalloc(1, CP_BUFFER_BYTES); + cpArrayPush(space->allocatedBuffers, buffer); + + for(int i=0; ipooledArbiters, buffer + i); + } + + return cpArbiterInit((cpArbiter *)cpArrayPop(space->pooledArbiters), shapes[0], shapes[1]); +} + +static inline cpBool +QueryRejectConstraint(cpBody *a, cpBody *b) +{ + CP_BODY_FOREACH_CONSTRAINT(a, constraint){ + if( + !constraint->collideBodies && ( + (constraint->a == a && constraint->b == b) || + (constraint->a == b && constraint->b == a) + ) + ) return cpTrue; + } + + return cpFalse; +} + +static inline cpBool +QueryReject(cpShape *a, cpShape *b) +{ + return ( + // BBoxes must overlap + !cpBBIntersects(a->bb, b->bb) + // Don't collide shapes attached to the same body. + || a->body == b->body + // Don't collide shapes that are filtered. + || cpShapeFilterReject(a->filter, b->filter) + // Don't collide bodies if they have a constraint with collideBodies == cpFalse. + || QueryRejectConstraint(a->body, b->body) + ); +} + +// Callback from the spatial hash. +cpCollisionID +cpSpaceCollideShapes(cpShape *a, cpShape *b, cpCollisionID id, cpSpace *space) +{ + // Reject any of the simple cases + if(QueryReject(a,b)) return id; + + // Narrow-phase collision detection. + struct cpCollisionInfo info = cpCollide(a, b, id, cpContactBufferGetArray(space)); + + if(info.count == 0) return info.id; // Shapes are not colliding. + cpSpacePushContacts(space, info.count); + + // Get an arbiter from space->arbiterSet for the two shapes. + // This is where the persistant contact magic comes from. + const cpShape *shape_pair[] = {info.a, info.b}; + cpHashValue arbHashID = CP_HASH_PAIR((cpHashValue)info.a, (cpHashValue)info.b); + cpArbiter *arb = (cpArbiter *)cpHashSetInsert(space->cachedArbiters, arbHashID, shape_pair, (cpHashSetTransFunc)cpSpaceArbiterSetTrans, space); + cpArbiterUpdate(arb, &info, space); + + cpCollisionHandler *handler = arb->handler; + + // Call the begin function first if it's the first step + if(arb->state == CP_ARBITER_STATE_FIRST_COLLISION && !handler->beginFunc(arb, space, handler->userData)){ + cpArbiterIgnore(arb); // permanently ignore the collision until separation + } + + if( + // Ignore the arbiter if it has been flagged + (arb->state != CP_ARBITER_STATE_IGNORE) && + // Call preSolve + handler->preSolveFunc(arb, space, handler->userData) && + // Check (again) in case the pre-solve() callback called cpArbiterIgnored(). + arb->state != CP_ARBITER_STATE_IGNORE && + // Process, but don't add collisions for sensors. + !(a->sensor || b->sensor) && + // Don't process collisions between two infinite mass bodies. + // This includes collisions between two kinematic bodies, or a kinematic body and a static body. + !(a->body->m == INFINITY && b->body->m == INFINITY) + ){ + cpArrayPush(space->arbiters, arb); + } else { + cpSpacePopContacts(space, info.count); + + arb->contacts = NULL; + arb->count = 0; + + // Normally arbiters are set as used after calling the post-solve callback. + // However, post-solve() callbacks are not called for sensors or arbiters rejected from pre-solve. + if(arb->state != CP_ARBITER_STATE_IGNORE) arb->state = CP_ARBITER_STATE_NORMAL; + } + + // Time stamp the arbiter so we know it was used recently. + arb->stamp = space->stamp; + return info.id; +} + +// Hashset filter func to throw away old arbiters. +cpBool +cpSpaceArbiterSetFilter(cpArbiter *arb, cpSpace *space) +{ + cpTimestamp ticks = space->stamp - arb->stamp; + + cpBody *a = arb->body_a, *b = arb->body_b; + + // TODO: should make an arbiter state for this so it doesn't require filtering arbiters for dangling body pointers on body removal. + // Preserve arbiters on sensors and rejected arbiters for sleeping objects. + // This prevents errant separate callbacks from happenening. + if( + (cpBodyGetType(a) == CP_BODY_TYPE_STATIC || cpBodyIsSleeping(a)) && + (cpBodyGetType(b) == CP_BODY_TYPE_STATIC || cpBodyIsSleeping(b)) + ){ + return cpTrue; + } + + // Arbiter was used last frame, but not this one + if(ticks >= 1 && arb->state != CP_ARBITER_STATE_CACHED){ + arb->state = CP_ARBITER_STATE_CACHED; + cpCollisionHandler *handler = arb->handler; + handler->separateFunc(arb, space, handler->userData); + } + + if(ticks >= space->collisionPersistence){ + arb->contacts = NULL; + arb->count = 0; + + cpArrayPush(space->pooledArbiters, arb); + return cpFalse; + } + + return cpTrue; +} + +//MARK: All Important cpSpaceStep() Function + + void +cpShapeUpdateFunc(cpShape *shape, void *unused) +{ + cpShapeCacheBB(shape); +} + +void +cpSpaceStep(cpSpace *space, cpFloat dt) +{ + // don't step if the timestep is 0! + if(dt == 0.0f) return; + + space->stamp++; + + cpFloat prev_dt = space->curr_dt; + space->curr_dt = dt; + + cpArray *bodies = space->dynamicBodies; + cpArray *constraints = space->constraints; + cpArray *arbiters = space->arbiters; + + // Reset and empty the arbiter lists. + for(int i=0; inum; i++){ + cpArbiter *arb = (cpArbiter *)arbiters->arr[i]; + arb->state = CP_ARBITER_STATE_NORMAL; + + // If both bodies are awake, unthread the arbiter from the contact graph. + if(!cpBodyIsSleeping(arb->body_a) && !cpBodyIsSleeping(arb->body_b)){ + cpArbiterUnthread(arb); + } + } + arbiters->num = 0; + + cpSpaceLock(space); { + // Integrate positions + for(int i=0; inum; i++){ + cpBody *body = (cpBody *)bodies->arr[i]; + body->position_func(body, dt); + } + + // Find colliding pairs. + cpSpacePushFreshContactBuffer(space); + cpSpatialIndexEach(space->dynamicShapes, (cpSpatialIndexIteratorFunc)cpShapeUpdateFunc, NULL); + cpSpatialIndexReindexQuery(space->dynamicShapes, (cpSpatialIndexQueryFunc)cpSpaceCollideShapes, space); + } cpSpaceUnlock(space, cpFalse); + + // Rebuild the contact graph (and detect sleeping components if sleeping is enabled) + cpSpaceProcessComponents(space, dt); + + cpSpaceLock(space); { + // Clear out old cached arbiters and call separate callbacks + cpHashSetFilter(space->cachedArbiters, (cpHashSetFilterFunc)cpSpaceArbiterSetFilter, space); + + // Prestep the arbiters and constraints. + cpFloat slop = space->collisionSlop; + cpFloat biasCoef = 1.0f - cpfpow(space->collisionBias, dt); + for(int i=0; inum; i++){ + cpArbiterPreStep((cpArbiter *)arbiters->arr[i], dt, slop, biasCoef); + } + + for(int i=0; inum; i++){ + cpConstraint *constraint = (cpConstraint *)constraints->arr[i]; + + cpConstraintPreSolveFunc preSolve = constraint->preSolve; + if(preSolve) preSolve(constraint, space); + + constraint->klass->preStep(constraint, dt); + } + + // Integrate velocities. + cpFloat damping = cpfpow(space->damping, dt); + cpVect gravity = space->gravity; + for(int i=0; inum; i++){ + cpBody *body = (cpBody *)bodies->arr[i]; + body->velocity_func(body, gravity, damping, dt); + } + + // Apply cached impulses + cpFloat dt_coef = (prev_dt == 0.0f ? 0.0f : dt/prev_dt); + for(int i=0; inum; i++){ + cpArbiterApplyCachedImpulse((cpArbiter *)arbiters->arr[i], dt_coef); + } + + for(int i=0; inum; i++){ + cpConstraint *constraint = (cpConstraint *)constraints->arr[i]; + constraint->klass->applyCachedImpulse(constraint, dt_coef); + } + + // Run the impulse solver. + for(int i=0; iiterations; i++){ + for(int j=0; jnum; j++){ + cpArbiterApplyImpulse((cpArbiter *)arbiters->arr[j]); + } + + for(int j=0; jnum; j++){ + cpConstraint *constraint = (cpConstraint *)constraints->arr[j]; + constraint->klass->applyImpulse(constraint, dt); + } + } + + // Run the constraint post-solve callbacks + for(int i=0; inum; i++){ + cpConstraint *constraint = (cpConstraint *)constraints->arr[i]; + + cpConstraintPostSolveFunc postSolve = constraint->postSolve; + if(postSolve) postSolve(constraint, space); + } + + // run the post-solve callbacks + for(int i=0; inum; i++){ + cpArbiter *arb = (cpArbiter *) arbiters->arr[i]; + + cpCollisionHandler *handler = arb->handler; + handler->postSolveFunc(arb, space, handler->userData); + } + } cpSpaceUnlock(space, cpTrue); +} diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpatialIndex.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpatialIndex.c" new file mode 100644 index 0000000..3fb7cb5 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpatialIndex.c" @@ -0,0 +1,69 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +void +cpSpatialIndexFree(cpSpatialIndex *index) +{ + if(index){ + cpSpatialIndexDestroy(index); + cpfree(index); + } +} + +cpSpatialIndex * +cpSpatialIndexInit(cpSpatialIndex *index, cpSpatialIndexClass *klass, cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex) +{ + index->klass = klass; + index->bbfunc = bbfunc; + index->staticIndex = staticIndex; + + if(staticIndex){ + cpAssertHard(!staticIndex->dynamicIndex, "This static index is already associated with a dynamic index."); + staticIndex->dynamicIndex = index; + } + + return index; +} + +typedef struct dynamicToStaticContext { + cpSpatialIndexBBFunc bbfunc; + cpSpatialIndex *staticIndex; + cpSpatialIndexQueryFunc queryFunc; + void *data; +} dynamicToStaticContext; + +static void +dynamicToStaticIter(void *obj, dynamicToStaticContext *context) +{ + cpSpatialIndexQuery(context->staticIndex, obj, context->bbfunc(obj), context->queryFunc, context->data); +} + +void +cpSpatialIndexCollideStatic(cpSpatialIndex *dynamicIndex, cpSpatialIndex *staticIndex, cpSpatialIndexQueryFunc func, void *data) +{ + if(staticIndex && cpSpatialIndexCount(staticIndex) > 0){ + dynamicToStaticContext context = {dynamicIndex->bbfunc, staticIndex, func, data}; + cpSpatialIndexEach(dynamicIndex, (cpSpatialIndexIteratorFunc)dynamicToStaticIter, &context); + } +} + diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSweep1D.c" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSweep1D.c" new file mode 100644 index 0000000..e200cf9 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSweep1D.c" @@ -0,0 +1,254 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +#include "chipmunk/chipmunk_private.h" + +static inline cpSpatialIndexClass *Klass(); + +//MARK: Basic Structures + +typedef struct Bounds { + cpFloat min, max; +} Bounds; + +typedef struct TableCell { + void *obj; + Bounds bounds; +} TableCell; + +struct cpSweep1D +{ + cpSpatialIndex spatialIndex; + + int num; + int max; + TableCell *table; +}; + +static inline cpBool +BoundsOverlap(Bounds a, Bounds b) +{ + return (a.min <= b.max && b.min <= a.max); +} + +static inline Bounds +BBToBounds(cpSweep1D *sweep, cpBB bb) +{ + Bounds bounds = {bb.l, bb.r}; + return bounds; +} + +static inline TableCell +MakeTableCell(cpSweep1D *sweep, void *obj) +{ + TableCell cell = {obj, BBToBounds(sweep, sweep->spatialIndex.bbfunc(obj))}; + return cell; +} + +//MARK: Memory Management Functions + +cpSweep1D * +cpSweep1DAlloc(void) +{ + return (cpSweep1D *)cpcalloc(1, sizeof(cpSweep1D)); +} + +static void +ResizeTable(cpSweep1D *sweep, int size) +{ + sweep->max = size; + sweep->table = (TableCell *)cprealloc(sweep->table, size*sizeof(TableCell)); +} + +cpSpatialIndex * +cpSweep1DInit(cpSweep1D *sweep, cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex) +{ + cpSpatialIndexInit((cpSpatialIndex *)sweep, Klass(), bbfunc, staticIndex); + + sweep->num = 0; + ResizeTable(sweep, 32); + + return (cpSpatialIndex *)sweep; +} + +cpSpatialIndex * +cpSweep1DNew(cpSpatialIndexBBFunc bbfunc, cpSpatialIndex *staticIndex) +{ + return cpSweep1DInit(cpSweep1DAlloc(), bbfunc, staticIndex); +} + +static void +cpSweep1DDestroy(cpSweep1D *sweep) +{ + cpfree(sweep->table); + sweep->table = NULL; +} + +//MARK: Misc + +static int +cpSweep1DCount(cpSweep1D *sweep) +{ + return sweep->num; +} + +static void +cpSweep1DEach(cpSweep1D *sweep, cpSpatialIndexIteratorFunc func, void *data) +{ + TableCell *table = sweep->table; + for(int i=0, count=sweep->num; itable; + for(int i=0, count=sweep->num; inum == sweep->max) ResizeTable(sweep, sweep->max*2); + + sweep->table[sweep->num] = MakeTableCell(sweep, obj); + sweep->num++; +} + +static void +cpSweep1DRemove(cpSweep1D *sweep, void *obj, cpHashValue hashid) +{ + TableCell *table = sweep->table; + for(int i=0, count=sweep->num; inum; + + table[i] = table[num]; + table[num].obj = NULL; + + return; + } + } +} + +//MARK: Reindexing Functions + +static void +cpSweep1DReindexObject(cpSweep1D *sweep, void *obj, cpHashValue hashid) +{ + // Nothing to do here +} + +static void +cpSweep1DReindex(cpSweep1D *sweep) +{ + // Nothing to do here + // Could perform a sort, but queries are not accelerated anyway. +} + +//MARK: Query Functions + +static void +cpSweep1DQuery(cpSweep1D *sweep, void *obj, cpBB bb, cpSpatialIndexQueryFunc func, void *data) +{ + // Implementing binary search here would allow you to find an upper limit + // but not a lower limit. Probably not worth the hassle. + + Bounds bounds = BBToBounds(sweep, bb); + + TableCell *table = sweep->table; + for(int i=0, count=sweep->num; itable; + for(int i=0, count=sweep->num; ibounds.min < b->bounds.min ? -1 : (a->bounds.min > b->bounds.min ? 1 : 0)); +} + +static void +cpSweep1DReindexQuery(cpSweep1D *sweep, cpSpatialIndexQueryFunc func, void *data) +{ + TableCell *table = sweep->table; + int count = sweep->num; + + // Update bounds and sort + for(int i=0; ispatialIndex.staticIndex, func, data); +} + +static cpSpatialIndexClass klass = { + (cpSpatialIndexDestroyImpl)cpSweep1DDestroy, + + (cpSpatialIndexCountImpl)cpSweep1DCount, + (cpSpatialIndexEachImpl)cpSweep1DEach, + (cpSpatialIndexContainsImpl)cpSweep1DContains, + + (cpSpatialIndexInsertImpl)cpSweep1DInsert, + (cpSpatialIndexRemoveImpl)cpSweep1DRemove, + + (cpSpatialIndexReindexImpl)cpSweep1DReindex, + (cpSpatialIndexReindexObjectImpl)cpSweep1DReindexObject, + (cpSpatialIndexReindexQueryImpl)cpSweep1DReindexQuery, + + (cpSpatialIndexQueryImpl)cpSweep1DQuery, + (cpSpatialIndexSegmentQueryImpl)cpSweep1DSegmentQuery, +}; + +static inline cpSpatialIndexClass *Klass(){return &klass;} + diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/prime.h" "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/prime.h" new file mode 100644 index 0000000..d470c2c --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/prime.h" @@ -0,0 +1,68 @@ +/* Copyright (c) 2013 Scott Lembcke and Howling Moon Software + * + * 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. + */ + +// Used for resizing hash tables. +// Values approximately double. +// http://planetmath.org/encyclopedia/GoodHashTablePrimes.html +static int primes[] = { + 5, + 13, + 23, + 47, + 97, + 193, + 389, + 769, + 1543, + 3079, + 6151, + 12289, + 24593, + 49157, + 98317, + 196613, + 393241, + 786433, + 1572869, + 3145739, + 6291469, + 12582917, + 25165843, + 50331653, + 100663319, + 201326611, + 402653189, + 805306457, + 1610612741, + 0, +}; + +static inline int +next_prime(int n) +{ + int i = 0; + while(n > primes[i]){ + i++; + cpAssertHard(primes[i], "Tried to resize a hash table to a size greater than 1610612741 O_o"); // realistically this should never happen + } + + return primes[i]; +} diff --git "a/madcars/examples/\321\201++17chipmunk/solution/strategy.cpp" "b/madcars/examples/\321\201++17chipmunk/solution/strategy.cpp" new file mode 100644 index 0000000..fd95d43 --- /dev/null +++ "b/madcars/examples/\321\201++17chipmunk/solution/strategy.cpp" @@ -0,0 +1,103 @@ +#include +#include "../chipmunk_src/include/chipmunk/chipmunk_private.h" +#include "../../nlohmann/json.hpp" + +using namespace std; + +#define PI 3.14159265358979323846264338327950288 + +int main() { + cpSpace* space = cpSpaceNew(); + string input_string, input_type; + int tick(0); + int round(-1); + while (true) { + + getline(cin, input_string); + auto state = nlohmann::json::parse(input_string); + // also possible: + // json state; + // cin >> state; + + input_type = state["type"].get(); + auto params = state["params"]; + + if (input_type == "new_match") { + int my_lives = params["my_lives"].get(); + int enemy_lives = params["enemy_lives"].get(); + + // Example of proto_map parsing. + auto map = params["proto_map"]; + int map_id = map["external_id"].get(); + auto segments = map["segments"]; + for(auto segment:segments){ + auto fp = segment[0]; + auto sp = segment[1]; + double height = segment[2].get(); + } + + auto proto_car = params["proto_car"]; + // etc... + + round++; + tick = 0; + + //cerr << "Round " << round + 1 << " started!" << endl; + + } else if (input_type == "tick") { + //cerr << "Round " << round + 1; + //cerr << " tick " << tick << endl; + + nlohmann::json command; + if (tick < 20) { + // wait a little bit to fall on floor + command["command"] = "stop"; + //cerr << "Waiting..." << endl; + } else { + auto my_car = params["my_car"]; + auto enemy_car = params["enemy_car"]; + + auto my_pos = my_car[0]; + auto enemy_pos = enemy_car[0]; + + // check my and enemy position and go to the enemy + if(my_pos[0].get() > enemy_pos[0].get()) { + command["command"] = "left"; + } else { + command["command"] = "right"; + } + + // roll over in air prevention (this feature can lead to death) + double my_angle = my_car[1].get(); + + // normalize angle + while (my_angle > PI) { + my_angle -= 2.0 * PI; + } + while (my_angle < -PI) { + my_angle += 2.0 * PI; + } + + if (my_angle > PI / 4.0) { + //cerr << "Uhh!" << endl; + command["command"] = "left"; + } else if (my_angle < -PI / 4.0) { + //cerr << "Ahh!" << endl; + command["command"] = "right"; + } else { + //cerr << "Attack!" << endl; + } + } + + //cerr << command.dump() << endl; + cout << command.dump() << endl; + + tick++; + } else { + //cerr << "end_game " << endl; + break; + } + } + + return 0; +} From d1fcdea67b3a7d280d684f983b4324ec0e49bbad Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Sat, 8 Sep 2018 23:02:47 +0300 Subject: [PATCH 32/96] dir rename --- .../c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_ffi.h | 0 .../chipmunk_src/include/chipmunk/chipmunk_private.h | 0 .../chipmunk_src/include/chipmunk/chipmunk_structs.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_types.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_unsafe.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpArbiter.h | 0 .../examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpBB.h | 0 .../examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpBody.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpConstraint.h | 0 .../chipmunk_src/include/chipmunk/cpDampedRotarySpring.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpDampedSpring.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpGearJoint.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpGrooveJoint.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpHastySpace.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpMarch.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpPinJoint.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpPivotJoint.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpPolyShape.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpPolyline.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpRatchetJoint.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpRobust.h | 0 .../chipmunk_src/include/chipmunk/cpRotaryLimitJoint.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpShape.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpSimpleMotor.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpSlideJoint.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpSpace.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpSpatialIndex.h | 0 .../c++17chipmunk/chipmunk_src/include/chipmunk/cpTransform.h | 0 .../examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpVect.h | 0 .../examples/c++17chipmunk/chipmunk_src/src/chipmunk.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpArbiter.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpArray.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpBBTree.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpBody.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpCollision.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpConstraint.c | 0 .../c++17chipmunk/chipmunk_src/src/cpDampedRotarySpring.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpDampedSpring.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpGearJoint.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpGrooveJoint.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpHashSet.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpHastySpace.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpMarch.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpPinJoint.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpPivotJoint.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpPolyShape.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpPolyline.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpRatchetJoint.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpRobust.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpRotaryLimitJoint.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpShape.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpSimpleMotor.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpSlideJoint.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpSpace.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpSpaceComponent.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpSpaceDebug.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpSpaceHash.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpSpaceQuery.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpSpaceStep.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpSpatialIndex.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/cpSweep1D.c | 0 .../examples/c++17chipmunk/chipmunk_src/src/prime.h | 0 .../examples/c++17chipmunk/solution/strategy.cpp | 0 64 files changed, 0 insertions(+), 0 deletions(-) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_ffi.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_ffi.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_private.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_private.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_structs.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_structs.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_types.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_types.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_unsafe.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_unsafe.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpArbiter.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpArbiter.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBB.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpBB.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBody.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpBody.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpConstraint.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpConstraint.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedRotarySpring.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpDampedRotarySpring.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedSpring.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpDampedSpring.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGearJoint.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpGearJoint.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGrooveJoint.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpGrooveJoint.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpHastySpace.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpHastySpace.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpMarch.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpMarch.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPinJoint.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpPinJoint.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPivotJoint.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpPivotJoint.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyShape.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpPolyShape.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyline.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpPolyline.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRatchetJoint.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpRatchetJoint.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRobust.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpRobust.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRotaryLimitJoint.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpRotaryLimitJoint.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpShape.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpShape.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSimpleMotor.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpSimpleMotor.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSlideJoint.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpSlideJoint.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpace.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpSpace.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpatialIndex.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpSpatialIndex.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpTransform.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpTransform.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpVect.h" => madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpVect.h (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/chipmunk.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/chipmunk.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArbiter.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpArbiter.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArray.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpArray.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBBTree.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpBBTree.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBody.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpBody.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpCollision.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpCollision.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpConstraint.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpConstraint.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedRotarySpring.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpDampedRotarySpring.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedSpring.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpDampedSpring.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGearJoint.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpGearJoint.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGrooveJoint.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpGrooveJoint.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHashSet.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpHashSet.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHastySpace.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpHastySpace.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpMarch.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpMarch.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPinJoint.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpPinJoint.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPivotJoint.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpPivotJoint.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyShape.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpPolyShape.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyline.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpPolyline.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRatchetJoint.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpRatchetJoint.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRobust.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpRobust.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRotaryLimitJoint.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpRotaryLimitJoint.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpShape.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpShape.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSimpleMotor.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpSimpleMotor.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSlideJoint.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpSlideJoint.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpace.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpace.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceComponent.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceComponent.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceDebug.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceDebug.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceHash.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceHash.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceQuery.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceQuery.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceStep.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceStep.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpatialIndex.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpatialIndex.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSweep1D.c" => madcars/examples/c++17chipmunk/chipmunk_src/src/cpSweep1D.c (100%) rename "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/prime.h" => madcars/examples/c++17chipmunk/chipmunk_src/src/prime.h (100%) rename "madcars/examples/\321\201++17chipmunk/solution/strategy.cpp" => madcars/examples/c++17chipmunk/solution/strategy.cpp (100%) diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_ffi.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_ffi.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_ffi.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_ffi.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_private.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_private.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_private.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_private.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_structs.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_structs.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_structs.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_structs.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_types.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_types.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_types.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_types.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_unsafe.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_unsafe.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_unsafe.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/chipmunk_unsafe.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpArbiter.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpArbiter.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpArbiter.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpArbiter.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBB.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpBB.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBB.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpBB.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBody.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpBody.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpBody.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpBody.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpConstraint.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpConstraint.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpConstraint.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpConstraint.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedRotarySpring.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpDampedRotarySpring.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedRotarySpring.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpDampedRotarySpring.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedSpring.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpDampedSpring.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpDampedSpring.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpDampedSpring.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGearJoint.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpGearJoint.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGearJoint.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpGearJoint.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGrooveJoint.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpGrooveJoint.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpGrooveJoint.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpGrooveJoint.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpHastySpace.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpHastySpace.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpHastySpace.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpHastySpace.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpMarch.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpMarch.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpMarch.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpMarch.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPinJoint.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpPinJoint.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPinJoint.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpPinJoint.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPivotJoint.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpPivotJoint.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPivotJoint.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpPivotJoint.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyShape.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpPolyShape.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyShape.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpPolyShape.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyline.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpPolyline.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpPolyline.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpPolyline.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRatchetJoint.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpRatchetJoint.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRatchetJoint.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpRatchetJoint.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRobust.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpRobust.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRobust.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpRobust.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRotaryLimitJoint.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpRotaryLimitJoint.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpRotaryLimitJoint.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpRotaryLimitJoint.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpShape.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpShape.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpShape.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpShape.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSimpleMotor.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpSimpleMotor.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSimpleMotor.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpSimpleMotor.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSlideJoint.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpSlideJoint.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSlideJoint.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpSlideJoint.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpace.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpSpace.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpace.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpSpace.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpatialIndex.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpSpatialIndex.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpSpatialIndex.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpSpatialIndex.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpTransform.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpTransform.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpTransform.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpTransform.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpVect.h" b/madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpVect.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/include/chipmunk/cpVect.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/include/chipmunk/cpVect.h diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/chipmunk.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/chipmunk.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/chipmunk.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/chipmunk.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArbiter.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpArbiter.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArbiter.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpArbiter.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArray.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpArray.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpArray.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpArray.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBBTree.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpBBTree.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBBTree.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpBBTree.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBody.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpBody.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpBody.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpBody.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpCollision.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpCollision.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpCollision.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpCollision.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpConstraint.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpConstraint.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpConstraint.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpConstraint.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedRotarySpring.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpDampedRotarySpring.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedRotarySpring.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpDampedRotarySpring.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedSpring.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpDampedSpring.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpDampedSpring.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpDampedSpring.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGearJoint.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpGearJoint.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGearJoint.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpGearJoint.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGrooveJoint.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpGrooveJoint.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpGrooveJoint.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpGrooveJoint.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHashSet.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpHashSet.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHashSet.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpHashSet.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHastySpace.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpHastySpace.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpHastySpace.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpHastySpace.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpMarch.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpMarch.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpMarch.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpMarch.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPinJoint.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpPinJoint.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPinJoint.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpPinJoint.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPivotJoint.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpPivotJoint.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPivotJoint.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpPivotJoint.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyShape.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpPolyShape.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyShape.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpPolyShape.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyline.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpPolyline.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpPolyline.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpPolyline.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRatchetJoint.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpRatchetJoint.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRatchetJoint.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpRatchetJoint.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRobust.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpRobust.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRobust.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpRobust.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRotaryLimitJoint.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpRotaryLimitJoint.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpRotaryLimitJoint.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpRotaryLimitJoint.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpShape.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpShape.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpShape.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpShape.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSimpleMotor.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpSimpleMotor.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSimpleMotor.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpSimpleMotor.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSlideJoint.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpSlideJoint.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSlideJoint.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpSlideJoint.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpace.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpace.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpace.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpace.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceComponent.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceComponent.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceComponent.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceComponent.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceDebug.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceDebug.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceDebug.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceDebug.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceHash.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceHash.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceHash.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceHash.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceQuery.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceQuery.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceQuery.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceQuery.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceStep.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceStep.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpaceStep.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpaceStep.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpatialIndex.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpatialIndex.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSpatialIndex.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpSpatialIndex.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSweep1D.c" b/madcars/examples/c++17chipmunk/chipmunk_src/src/cpSweep1D.c similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/cpSweep1D.c" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/cpSweep1D.c diff --git "a/madcars/examples/\321\201++17chipmunk/chipmunk_src/src/prime.h" b/madcars/examples/c++17chipmunk/chipmunk_src/src/prime.h similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/chipmunk_src/src/prime.h" rename to madcars/examples/c++17chipmunk/chipmunk_src/src/prime.h diff --git "a/madcars/examples/\321\201++17chipmunk/solution/strategy.cpp" b/madcars/examples/c++17chipmunk/solution/strategy.cpp similarity index 100% rename from "madcars/examples/\321\201++17chipmunk/solution/strategy.cpp" rename to madcars/examples/c++17chipmunk/solution/strategy.cpp From d8a4b2d7958cb864ec6b9c9b3042762b78f3a13a Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Tue, 11 Sep 2018 00:00:03 +0300 Subject: [PATCH 33/96] deadline fix --- madcars/Runners/mechanic/game_objects/deadline.py | 6 ++---- madcars/Runners/mechanic/match.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/madcars/Runners/mechanic/game_objects/deadline.py b/madcars/Runners/mechanic/game_objects/deadline.py index 9e26267..880f900 100644 --- a/madcars/Runners/mechanic/game_objects/deadline.py +++ b/madcars/Runners/mechanic/game_objects/deadline.py @@ -5,12 +5,10 @@ class DeadLine: ASC = 0 DESC = 1 - def __init__(self, type, max_length, max_height, space): + def __init__(self, type, max_length, max_height): self.type = type - fp = (0, 0) - sp = (max_length, 0) self.line_body = pymunk.Body(0, 0, pymunk.Body.KINEMATIC) - self.line = pymunk.Segment(self.line_body, fp, sp, 2) + self.line = pymunk.Poly(self.line_body, [(0, 2), (max_length, 2), (max_length, -max_height), (0, -max_height)]) self.line.sensor = True self.line.body.position = (0, 10 if self.type == self.ASC else max_height - 10) diff --git a/madcars/Runners/mechanic/match.py b/madcars/Runners/mechanic/match.py index 402533c..eecd5a8 100644 --- a/madcars/Runners/mechanic/match.py +++ b/madcars/Runners/mechanic/match.py @@ -17,7 +17,7 @@ def __init__(self, map, car, players, space): self.players = players self.map_objects = map(space).get_objects_for_space() self.cars_objects = [] - self.deadline = DeadLine(DeadLine.ASC, 1800, 800, space) + self.deadline = DeadLine(DeadLine.ASC, 1800, 800) self.map_objects.append(self.deadline.get_object_for_space()) self.dead_players = set() From d103b9a72416facc6016eb713f4c18406b8aaebf Mon Sep 17 00:00:00 2001 From: AdmAlexus Date: Tue, 11 Sep 2018 12:09:45 +0500 Subject: [PATCH 34/96] make php rand command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В первоначальной версии примера каждый тик машина двигалась только направо. Поправил на рандомную команду --- madcars/examples/php/main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/madcars/examples/php/main.php b/madcars/examples/php/main.php index 5e70803..d8670a9 100644 --- a/madcars/examples/php/main.php +++ b/madcars/examples/php/main.php @@ -5,7 +5,7 @@ $parsed = json_decode($line); $commands = array('right', 'left', 'stop'); $rand_key = array_rand($commands); - $command = array('command' => 'right', 'debug' => 'debug_msg'); + $command = array('command' => $rand_key, 'debug' => 'debug_msg'); print json_encode($command)."\n"; } From 4d38bcc506581b036ca57c696d88a90a5b149ecd Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Fri, 14 Sep 2018 00:46:19 +0300 Subject: [PATCH 35/96] add its_alive to car for rest ticks --- madcars/Runners/localrunner.py | 2 +- .../Runners/mechanic/game_objects/base_car.py | 20 +++++++++++++------ madcars/Runners/mechanic/match.py | 3 ++- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/madcars/Runners/localrunner.py b/madcars/Runners/localrunner.py index 0f40be9..2e31dd3 100644 --- a/madcars/Runners/localrunner.py +++ b/madcars/Runners/localrunner.py @@ -65,7 +65,7 @@ def on_draw(): window.clear() game.draw(draw_options) if not game.game_complete: - future_message = loop.run_until_complete(game.tick()) + loop.run_until_complete(game.tick()) else: winner = game.get_winner() if winner: diff --git a/madcars/Runners/mechanic/game_objects/base_car.py b/madcars/Runners/mechanic/game_objects/base_car.py index c5bd5f4..b3ec22b 100644 --- a/madcars/Runners/mechanic/game_objects/base_car.py +++ b/madcars/Runners/mechanic/game_objects/base_car.py @@ -76,6 +76,8 @@ def __init__(self, car_group, direction, point_query_nearest): self.point_query_nearest = point_query_nearest + self.its_alive = True + def create_wheel(self, wheel_side): if wheel_side not in ['rear', 'front']: raise Exception('Wheel position must be front or rear') @@ -196,11 +198,18 @@ def in_air(self): def get_button_collision_type(self): return self.button_collision_type - def fast_dump(self): - return [(self.car_body.position.x, self.car_body.position.y), - self.car_body.angle, self.x_modification, - (self.rear_wheel_body.position.x, self.rear_wheel_body.position.y, self.rear_wheel_body.angle), - (self.front_wheel_body.position.x, self.front_wheel_body.position.y, self.front_wheel_body.angle)] + def die(self): + self.its_alive = False + self.car_shape.color = 255, 0, 0 + + def fast_dump(self, visio=False): + result = [(self.car_body.position.x, self.car_body.position.y), + self.car_body.angle, self.x_modification, + (self.rear_wheel_body.position.x, self.rear_wheel_body.position.y, self.rear_wheel_body.angle), + (self.front_wheel_body.position.x, self.front_wheel_body.position.y, self.front_wheel_body.angle)] + if visio: + result.append(self.its_alive) + return result @classmethod def proto_dump(cls, visio=False): @@ -210,7 +219,6 @@ def proto_dump(cls, visio=False): 'front_wheel_radius': cls.front_wheel_radius, 'button_poly': cls.get_button_poly(), 'external_id': cls.external_id, - } if not visio: diff --git a/madcars/Runners/mechanic/match.py b/madcars/Runners/mechanic/match.py index eecd5a8..5a6adf5 100644 --- a/madcars/Runners/mechanic/match.py +++ b/madcars/Runners/mechanic/match.py @@ -93,7 +93,7 @@ def get_players_car(self, player=None): break return my, enemy else: - return {p.id: p.car.fast_dump()for p in self.players} + return {p.id: p.car.fast_dump(visio=True) for p in self.players} def send_new_match_message(self): proto_map = self.map.get_proto() @@ -139,6 +139,7 @@ def send_tick(self, game_tick): def lose_callback(self, player, arbiter, space, _): if not self.is_rest: self.dead_players.add(player) + player.car.die() return False def smbd_die(self): From 22e11313df7367733790a856f446637744626435 Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Sat, 15 Sep 2018 20:39:37 +0300 Subject: [PATCH 36/96] add drain to send --- madcars/Runners/mechanic/game.py | 9 ++++++--- madcars/Runners/mechanic/match.py | 10 +++++----- madcars/Runners/mechanic/player.py | 3 ++- madcars/Runners/mechanic/strategy.py | 5 +++++ 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/madcars/Runners/mechanic/game.py b/madcars/Runners/mechanic/game.py index e0fdc3a..4e28336 100644 --- a/madcars/Runners/mechanic/game.py +++ b/madcars/Runners/mechanic/game.py @@ -63,8 +63,6 @@ def __init__(self, clients, games_list, extended_save=True): self.game_log = [] - self.next_match() - @classmethod def parse_games(cls, games_list): for g in games_list: @@ -86,10 +84,12 @@ def get_winner(self): return winner[0] return False + @asyncio.coroutine def next_match(self): map, car = next(self.matches) self.clear_space() match = Match(map, car, self.all_players, self.space) + yield from match.send_new_match_message() self.space.add(match.get_objects_for_space()) self.current_match = match @@ -104,6 +104,9 @@ def game_loop(self): @asyncio.coroutine def tick(self): + if self.current_match is None: + yield from self.next_match() + yield from self.current_match.tick(self.tick_num) self.space.step(0.016) @@ -118,7 +121,7 @@ def tick(self): self.end_game() return 'end_game' - self.next_match() + yield from self.next_match() self.tick_num += 1 diff --git a/madcars/Runners/mechanic/match.py b/madcars/Runners/mechanic/match.py index 5a6adf5..245f6b5 100644 --- a/madcars/Runners/mechanic/match.py +++ b/madcars/Runners/mechanic/match.py @@ -39,8 +39,6 @@ def __init__(self, map, car, players, space): co.begin = partial(self.lose_callback, player) player.set_car(c) - self.send_new_match_message() - @asyncio.coroutine def apply_turn_wrapper(self, player, game_tick): if not self.is_rest: @@ -55,7 +53,7 @@ def tick(self, game_tick): if self.rest_counter > 0: self.rest_counter -= 1 - self.send_tick(game_tick) + yield from self.send_tick(game_tick) futures = [] for p in self.players: futures.append(asyncio.ensure_future(self.apply_turn_wrapper(p, game_tick))) @@ -95,6 +93,7 @@ def get_players_car(self, player=None): else: return {p.id: p.car.fast_dump(visio=True) for p in self.players} + @asyncio.coroutine def send_new_match_message(self): proto_map = self.map.get_proto() proto_car = self.car.proto_dump() @@ -110,13 +109,14 @@ def send_new_match_message(self): for p in self.players: my_lives, enemy_lives = self.get_players_lives(p) - p.send_message('new_match', { + yield from p.send_message('new_match', { 'my_lives': my_lives, 'enemy_lives': enemy_lives, 'proto_map': proto_map, 'proto_car': proto_car, }) + @asyncio.coroutine def send_tick(self, game_tick): self.match_log.append({ 'type': 'tick', @@ -130,7 +130,7 @@ def send_tick(self, game_tick): if not self.is_rest: for p in self.players: my_car, enemy_car = self.get_players_car(p) - p.send_message('tick', { + yield from p.send_message('tick', { 'my_car': my_car, 'enemy_car': enemy_car, 'deadline_position': self.deadline.get_position() diff --git a/madcars/Runners/mechanic/player.py b/madcars/Runners/mechanic/player.py index 09a4d73..140389e 100644 --- a/madcars/Runners/mechanic/player.py +++ b/madcars/Runners/mechanic/player.py @@ -56,11 +56,12 @@ def apply_turn(self, tick): self.is_disconnected = True self.client.close() + @asyncio.coroutine def send_message(self, t, d): if self.is_disconnected: return try: - self.client.send_message(t, d) + yield from self.client.send_message(t, d) except Exception as e: print('write exception', self.client.get_solution_id(), e) self.is_disconnected = True diff --git a/madcars/Runners/mechanic/strategy.py b/madcars/Runners/mechanic/strategy.py index f359daf..9109ecb 100644 --- a/madcars/Runners/mechanic/strategy.py +++ b/madcars/Runners/mechanic/strategy.py @@ -13,12 +13,14 @@ class Client(object): + @asyncio.coroutine def get_command(self): pass def close(self): pass + @asyncio.coroutine def send_message(self, t, d): pass @@ -69,6 +71,7 @@ def __init__(self, path_to_script, path_to_log=None): else: self.path_to_log = path_to_log + @asyncio.coroutine def send_message(self, t, d): msg = { 'type': t, @@ -130,6 +133,7 @@ def set_solution_id(self): return bool(self.solution_id) + @asyncio.coroutine def send_message(self, t, d): msg = { 'type': t, @@ -137,6 +141,7 @@ def send_message(self, t, d): } msg_bytes = '{}\n'.format(json.dumps(msg)).encode() self.writer.write(msg_bytes) + yield from self.writer.drain() @asyncio.coroutine def get_command(self): From 9b383225cb22400d6e340cf27d494daece42eb28 Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Tue, 18 Sep 2018 22:07:02 +0300 Subject: [PATCH 37/96] change deadline --- madcars/Runners/mechanic/game_objects/deadline.py | 4 +++- madcars/Runners/mechanic/match.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/madcars/Runners/mechanic/game_objects/deadline.py b/madcars/Runners/mechanic/game_objects/deadline.py index 880f900..6349e43 100644 --- a/madcars/Runners/mechanic/game_objects/deadline.py +++ b/madcars/Runners/mechanic/game_objects/deadline.py @@ -4,11 +4,13 @@ class DeadLine: ASC = 0 DESC = 1 + deadline_height = 20 def __init__(self, type, max_length, max_height): self.type = type self.line_body = pymunk.Body(0, 0, pymunk.Body.KINEMATIC) - self.line = pymunk.Poly(self.line_body, [(0, 2), (max_length, 2), (max_length, -max_height), (0, -max_height)]) + self.line = pymunk.Poly(self.line_body, [(0, 2), (max_length, 2), + (max_length, -self.deadline_height), (0, -self.deadline_height)]) self.line.sensor = True self.line.body.position = (0, 10 if self.type == self.ASC else max_height - 10) diff --git a/madcars/Runners/mechanic/match.py b/madcars/Runners/mechanic/match.py index 245f6b5..47ccf7b 100644 --- a/madcars/Runners/mechanic/match.py +++ b/madcars/Runners/mechanic/match.py @@ -17,7 +17,7 @@ def __init__(self, map, car, players, space): self.players = players self.map_objects = map(space).get_objects_for_space() self.cars_objects = [] - self.deadline = DeadLine(DeadLine.ASC, 1800, 800) + self.deadline = DeadLine(DeadLine.ASC, 1200, 800) self.map_objects.append(self.deadline.get_object_for_space()) self.dead_players = set() From b8e5969c1482f7656e53c897b698553064ad0d75 Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Tue, 18 Sep 2018 23:45:24 +0300 Subject: [PATCH 38/96] add stop to dropped strategy --- madcars/Runners/mechanic/player.py | 1 + 1 file changed, 1 insertion(+) diff --git a/madcars/Runners/mechanic/player.py b/madcars/Runners/mechanic/player.py index 140389e..e9248f4 100644 --- a/madcars/Runners/mechanic/player.py +++ b/madcars/Runners/mechanic/player.py @@ -53,6 +53,7 @@ def apply_turn(self, tick): else: self.debug_log.append({'tick': tick, 'message': str(e)}) print('read exception', self.client.get_solution_id(), e) + self.car.stop() self.is_disconnected = True self.client.close() From 9313d952ddc4e06fa6310ef83ec59b7cd0a5b673 Mon Sep 17 00:00:00 2001 From: BorisKolganov Date: Tue, 25 Sep 2018 23:20:02 +0300 Subject: [PATCH 39/96] fix with empty turn --- madcars/Runners/mechanic/player.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/madcars/Runners/mechanic/player.py b/madcars/Runners/mechanic/player.py index e9248f4..f5d8ef3 100644 --- a/madcars/Runners/mechanic/player.py +++ b/madcars/Runners/mechanic/player.py @@ -45,6 +45,8 @@ def apply_turn(self, tick): else: action = random.choice((self.car.stop, self.car.go_left, self.car.go_right)) action() + else: + raise Exception('invalid turn') except Exception as e: args = e.args From 0ca41535299be57d20571bc9a856bee43bd3369b Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Thu, 18 Jul 2019 18:50:05 +0300 Subject: [PATCH 40/96] AiCups #4 --- paperio/QUICKSTART.md | 60 + paperio/README.md | 201 + paperio/dockers/base/Dockerfile | 18 + paperio/dockers/base/sources/client.pro | 20 + .../base/sources/compile_output_processor.py | 11 + paperio/dockers/base/sources/constants.h | 13 + paperio/dockers/base/sources/main.cpp | 85 + paperio/dockers/base/sources/run.sh | 26 + paperio/dockers/base/sources/tcp_client.h | 135 + paperio/dockers/c_sharp/Dockerfile | 18 + paperio/dockers/cpp11/Dockerfile | 10 + paperio/dockers/cpp11/nlohmann/LICENSE.MIT | 21 + paperio/dockers/cpp11/nlohmann/json.hpp | 17190 ++++++++++++++++ paperio/dockers/cpp17/Dockerfile | 18 + paperio/dockers/cpp17/Makefile | 7 + paperio/dockers/cpp17/nlohmann/LICENSE.MIT | 21 + paperio/dockers/cpp17/nlohmann/json.hpp | 17190 ++++++++++++++++ paperio/dockers/elixir/Dockerfile | 25 + paperio/dockers/elixir/mix.exs | 17 + paperio/dockers/golang/Dockerfile | 17 + paperio/dockers/haskell/Dockerfile | 17 + paperio/dockers/java1.8/Dockerfile | 22 + paperio/dockers/java1.8/pom.xml | 53 + paperio/dockers/java1.9/Dockerfile | 16 + paperio/dockers/java1.9/pom.xml | 53 + paperio/dockers/kotlin/Dockerfile | 18 + paperio/dockers/kotlin/build.gradle | 47 + paperio/dockers/nodejs9/Dockerfile | 9 + paperio/dockers/php7/Dockerfile | 10 + paperio/dockers/python2/Dockerfile | 11 + paperio/dockers/python3/Dockerfile | 10 + paperio/dockers/rust/Cargo.toml | 17 + paperio/dockers/rust/Dockerfile | 14 + paperio/dockers/scala/Dockerfile | 24 + paperio/dockers/scala/Main.scala | 41 + paperio/dockers/scala/assembly.sbt | 1 + paperio/dockers/scala/build.sbt | 12 + paperio/dockers/swift/Dockerfile | 17 + paperio/examples/cpp_strategy.cpp | 22 + paperio/examples/cs_strategy.cs | 15 + paperio/examples/go_strategy.go | 22 + paperio/examples/java9_strategy.java | 22 + paperio/examples/java_strategy.java | 19 + paperio/examples/js_strategy.js | 15 + paperio/examples/php_strategy.php | 12 + paperio/examples/python_strategy.py | 9 + paperio/examples/scala_strategy.scala | 12 + paperio/examples/server_python_strategy.py | 24 + paperio/examples/swift_strategy.swift | 10 + paperio/images/localrunner.png | Bin 0 -> 30353 bytes paperio/images/world.png | Bin 0 -> 46781 bytes paperio/local_runner/Dockerfile | 17 + paperio/local_runner/clients.py | 250 + paperio/local_runner/constants.py | 55 + paperio/local_runner/game_objects/__init__.py | 0 paperio/local_runner/game_objects/bonuses.py | 146 + paperio/local_runner/game_objects/game.py | 356 + paperio/local_runner/game_objects/player.py | 157 + paperio/local_runner/game_objects/scene.py | 59 + .../local_runner/game_objects/territory.py | 155 + paperio/local_runner/helpers.py | 204 + paperio/local_runner/localrunner.py | 76 + paperio/local_runner/requirements.txt | 2 + paperio/local_runner/serverrunner.py | 50 + paperio/local_runner/sprites/explorer.png | Bin 0 -> 33088 bytes paperio/local_runner/sprites/flash.png | Bin 0 -> 13323 bytes paperio/local_runner/sprites/saw.png | Bin 0 -> 25966 bytes 67 files changed, 37204 insertions(+) create mode 100644 paperio/QUICKSTART.md create mode 100644 paperio/README.md create mode 100644 paperio/dockers/base/Dockerfile create mode 100644 paperio/dockers/base/sources/client.pro create mode 100644 paperio/dockers/base/sources/compile_output_processor.py create mode 100644 paperio/dockers/base/sources/constants.h create mode 100644 paperio/dockers/base/sources/main.cpp create mode 100644 paperio/dockers/base/sources/run.sh create mode 100644 paperio/dockers/base/sources/tcp_client.h create mode 100644 paperio/dockers/c_sharp/Dockerfile create mode 100644 paperio/dockers/cpp11/Dockerfile create mode 100644 paperio/dockers/cpp11/nlohmann/LICENSE.MIT create mode 100644 paperio/dockers/cpp11/nlohmann/json.hpp create mode 100644 paperio/dockers/cpp17/Dockerfile create mode 100644 paperio/dockers/cpp17/Makefile create mode 100644 paperio/dockers/cpp17/nlohmann/LICENSE.MIT create mode 100644 paperio/dockers/cpp17/nlohmann/json.hpp create mode 100644 paperio/dockers/elixir/Dockerfile create mode 100644 paperio/dockers/elixir/mix.exs create mode 100644 paperio/dockers/golang/Dockerfile create mode 100644 paperio/dockers/haskell/Dockerfile create mode 100644 paperio/dockers/java1.8/Dockerfile create mode 100644 paperio/dockers/java1.8/pom.xml create mode 100644 paperio/dockers/java1.9/Dockerfile create mode 100644 paperio/dockers/java1.9/pom.xml create mode 100644 paperio/dockers/kotlin/Dockerfile create mode 100644 paperio/dockers/kotlin/build.gradle create mode 100644 paperio/dockers/nodejs9/Dockerfile create mode 100644 paperio/dockers/php7/Dockerfile create mode 100644 paperio/dockers/python2/Dockerfile create mode 100644 paperio/dockers/python3/Dockerfile create mode 100644 paperio/dockers/rust/Cargo.toml create mode 100644 paperio/dockers/rust/Dockerfile create mode 100644 paperio/dockers/scala/Dockerfile create mode 100644 paperio/dockers/scala/Main.scala create mode 100644 paperio/dockers/scala/assembly.sbt create mode 100644 paperio/dockers/scala/build.sbt create mode 100644 paperio/dockers/swift/Dockerfile create mode 100644 paperio/examples/cpp_strategy.cpp create mode 100644 paperio/examples/cs_strategy.cs create mode 100644 paperio/examples/go_strategy.go create mode 100644 paperio/examples/java9_strategy.java create mode 100644 paperio/examples/java_strategy.java create mode 100644 paperio/examples/js_strategy.js create mode 100644 paperio/examples/php_strategy.php create mode 100755 paperio/examples/python_strategy.py create mode 100644 paperio/examples/scala_strategy.scala create mode 100644 paperio/examples/server_python_strategy.py create mode 100644 paperio/examples/swift_strategy.swift create mode 100644 paperio/images/localrunner.png create mode 100644 paperio/images/world.png create mode 100644 paperio/local_runner/Dockerfile create mode 100644 paperio/local_runner/clients.py create mode 100644 paperio/local_runner/constants.py create mode 100644 paperio/local_runner/game_objects/__init__.py create mode 100644 paperio/local_runner/game_objects/bonuses.py create mode 100644 paperio/local_runner/game_objects/game.py create mode 100644 paperio/local_runner/game_objects/player.py create mode 100644 paperio/local_runner/game_objects/scene.py create mode 100644 paperio/local_runner/game_objects/territory.py create mode 100644 paperio/local_runner/helpers.py create mode 100644 paperio/local_runner/localrunner.py create mode 100644 paperio/local_runner/requirements.txt create mode 100644 paperio/local_runner/serverrunner.py create mode 100644 paperio/local_runner/sprites/explorer.png create mode 100644 paperio/local_runner/sprites/flash.png create mode 100644 paperio/local_runner/sprites/saw.png diff --git a/paperio/QUICKSTART.md b/paperio/QUICKSTART.md new file mode 100644 index 0000000..5154b63 --- /dev/null +++ b/paperio/QUICKSTART.md @@ -0,0 +1,60 @@ +## Быстрый старт + +## Установка необходимых пакетов + +Если у вас не установлен `Python`, то вам нужно будет установить `Python` >= `3.6` вместе с требованиями по пакетам, описанными в `requirements.txt`. + +Для того чтобы установить из списка **requirements** пакеты, вам понадобится менеджер пакетов Pip и виртуальное окружение **virtualenv**. Подробная инструкция установки - [здесь](https://packaging.python.org/guides/installing-using-pip-and-virtualenv/). + +Если у вас уже все установлено, то переходим к настройке окружения. + +1. **Создаем виртуальное окружение** +```$ virtualenv <название_окружения>``` +Название окружения может быть любым. + +2. **Активируем созданное виртуальное окружение** +```$ source <название_окружения>/bin/activate``` + +3. **Устанавливаем все зависимости из requirements.txt** +```$ pip install -r requirements.txt``` + +После всех установленных пакетов, чтобы запустить **Local Runner**, необходимо перейти в папку с файлом **localrunner.py** и исполнить команду + +```$ python3 localrunner.py``` + +У вас должен запуститься визуализатор. Поздравляем! + +**Внимание!** Подробнее про работу с LocalRunner вы можете прочитать в [правилах](README.md) в разделе [3](README.md#3-особенности-запуска-local-runner). + +## Загрузка решения на сайт +Для примера возьмем решение, написанное на Python: + +```python +import json +import random + +config = input() # получение конфигурации игры + +while True: + state = input() # получение тика + commands = ['left', 'right', 'up', 'down'] # доступные команды + cmd = random.choice(commands) # случайный выбор действия + print(json.dumps({"command": cmd, 'debug': cmd}) # отправка результата +``` + +* Сохраняем данное решение как main.py; +* Переходим на [сайт](https://aicups.ru/profile/); +* Нажимаем на кнопку `Отправить решение`; +* Выбираем язык `Python 3.6` и решение; +* Нажимаем `Отправить`. + +Ваше решение отправится в тестирующую систему. При возникновении каких-либо ошибок в синтаксисе и других ошибках, система вам об этом сообщит. + +После этого вам необходимо на [этой](https://aicups.ru/profile/#solutions) же странице выбрать решение, которое будет участвовать в рейтинговых и нерейтинговых играх. + +На этом все! Теперь можно сыграть с кем-либо, нажав на кнопку `Играть`. + +Мы обязательно рекомендуем вам прочитать [правила](README.md). + +Желаем удачи! + diff --git a/paperio/README.md b/paperio/README.md new file mode 100644 index 0000000..81deb56 --- /dev/null +++ b/paperio/README.md @@ -0,0 +1,201 @@ +# Правила участия в AiCups 4 + +Соревнование среди создателей искусственного интеллекта стартует в четвертый раз. На этот раз предлагаем вам написать своего **бота**, который сойдется в сложной битве против других ботов в игре по мотивам [Paper.io](http://paper-io.com/). + +Мероприятие проводится на специально разработанной онлайн-платформе [AiCups](http://aicups.ru/), где участники регистрируются, могут отправлять решения, стартовать игры и узнавать актуальные новости. + +Каждая игра принадлежит к классу рейтинговых, либо нерейтинговых. Нерейтинговые игры участники стартуют сами, они предназначены лишь для оценки собственных сил и отладки написанных ботов в боевом окружении. Рейтиновые игры система время от времени стартует самостоятельно, подбирая участников по алгоритму **TrueSkill**. По результатам рейтинговых игр формируется итоговый рейтинг и определяются победители. Каждая **игра** запускается на серверах организатора в изолированном окружении. + +Действия, которые будут представлять риск нанесения ущерба проверяющей системе, будут рассматриваться как нарушение данных правил и приведут к дисквалификации участника. + +Решения можно присылать на любом языке программирования из списка поддерживаемых: + +* C++11 / .zip, .h, .cpp +* C++14 / .zip, .h, .hpp, .cpp +* C++17 / .zip, .h, .hpp, .cpp +* C# / .zip, .cs +* Java1.8 / .zip, .java +* Kotlin / .zip, .kt +* Haskell / .zip, .hs +* Go / .zip, .go +* Python 2.7 / .zip, .py +* Python 3.6 / .zip, .py +* PHP7 / .zip, .php +* Node JS / .zip, .js +* Elixir / .zip, .ex +* Rust / .zip, .rs +* Scala / .zip, .scala + +Детальные инструкции по созданию своего решения, формату входных и выходных данных, сопутствующих пакетах и библиотеках можно прочитать в [разделе 2](#2-создание-решения). После того как решение было загружено и обработано, его результат можно посмотреть в визуализаторе на сайте. Попутно будут выводиться отладочный вывод и случившиеся ошибки. + +Для удобства участников, а также для снижения пиковых нагрузок на систему запуска, мы подготовили программу для локального запуска и отладки — **Local Runner** (далее обозначается как **LR** или **ЛР**). Она написана на Python 3.6 с использованием визуализатора Pyglet и поставляется в виде исходных кодов. Детальные инструкции по настройке и использованию Вы найдете в [разделе 3](#3-особенности-запуска-local-runner). + +Гайд по быстрому старту находится в файле [QUICKSTART.md](QUICKSTART.md). + +С организаторами соревнования можно связаться: + +* в группе Telegram [@aicups](https://t.me/aicups) +* с помощью формы обратной связи на сайте соревнования   + +Все числовые параметры, которые вы встретите ниже, даны как примеры. В каждой конкретной игре параметры могут быть немного изменены, что повлияет на игровой баланс и физику мира. Таким образом мы получаем более справедливый и предсказуемый рейтинг раундов. Конкретные параметры игры будут присылаться **боту** перед первым тиком (см. раздел 2). + +Удачи! + +## 1. Описание механики + +Игровой мир представляет собой квадрат со сторонами 930 на 930 условных единиц. Начало отсчета расположено в левом нижнем углу. Весь игровой мир разбит на 31*31 элементарных ячеек, размером 30 на 30. Все координаты в мире целого типа. +Суть игры крайне проста - вы играете за умный квадрат, постоянно находящийся в движении, направление движения которого вы можете менять. Также у вас есть небольшая территория, которую можно расширить, путем захвата новых клеток. За захват новых клеток и за некоторые другие игровые действия будут начисляться очки. Остальные игровые объекты, концепции и взаимодействия будут описаны ниже. + +![Изображение поля с осями координат и первоначальным расположением игроков](images/world.png) + +Как можно увидеть из рисунка, все боты равноудалены от ближайшей к ним границе и находятся на одинаковом расстоянии от двух ближайших к ним противников. + +Одновременно на одной карте соревнуются **шесть ботов**, созданных участниками соревнования. Каждому **боту** соответствует игровой объект типа "Игрок". Игра продолжается либо до смерти всех игроков, либо до максимального числа тиков (параметр конфигурации MAX_TICK_COUNT). Каждый тик выполняется по чёткой схеме: + * **ботам** передаётся состояние мира и в ответ ожидается **одна из команд - "left", "right", "up", "down"** ("right" - направление в сторону увеличения x, "left" - направление в сторону уменьшения x); + * каждая полученная команда тут же будет применена; + * при получении всех команд просчитывается новое состояние мира; + * за определенные игровые действия начисляются игровые очки и тот, кто наберет наибольшее количество очков к концу игры - станет победителем. + +### 1.1. Игровые объекты и концепции + +1. **Игрок** - двигающийся квадрат, который управляется **ботом** участника. Положение квадрата на карте определяется координатами его центра (x, y). Скорость квадрата задается параметром SPEED и может быть на время изменена, путем взятия бонуса. + +2. **Территория** - захваченная ботом область карты, на которой бот находиться в относительной безопасности. Территория может состоять из нескольких несвязанных частей. Так может получиться, например, из-за действий противников. + +3. **Шлейф** - пройденный игроком путь, вне своей территории. При возвращении игрока на свою территорию, все клетки между шлейфом и территорией, добавляются к территории игрока. При пересечении шлейфа другими игроками или при самопересечении своего шлейфа, игрок выбывает из игры, а захваченная им территория становиться нейтральной. + +4. **Бонус** - в игре имеется 3 вида бонусов - **Ускорение**, **Замедление** и **Пила**: + * **Ускорение** - увеличивает скорость игрока на несколько клеток. Количество клеток может быть любым, в диапазоне от 10 до 50; + * **Замедление** - уменьшает скорость игрока на несколько клеток. Количество клеток может быть любым, в диапазоне от 10 до 50; + * **Пила** - выстреливает лучом в направлении движения игрока. Применяется мгновенно. При пересечении лучом территории соперника, вся территория, противоположная положению соперника относительно данного луча, станет нейтральной. При пересечении лучом положения соперника, соперник выбывает, а вся его территория становится нейтральной. + +### 1.2. Игровые взаимодействия + +1. **Управляемое движение** осуществляется в 4 возможных направления. Движение в обратном направлении невозможно, и при получении системой данной команды, она будет проигнорирована. Движение осуществляется при помощи прибавления к текущей координате игрока значения SPEED каждый тик. При нахождении в состоянии перемещения между двумя клетками, поворот невозможен; + +2. **Захват территории** - осуществляется в момент возвращения на свою территорию с нейтральной либо вражеской. В этот момент все клетки внутри шлейфа присоединяются к территории игрока. В случае территории из нескольких частей шлейф игрока и границы его территории могут не образовывать замкнутую фигуру. Тогда к территории игрока будут добавлены только клетки пройденного пути; + +3. **Разрезание шлейфа** - при разрезании шлейфа противника, вам начисляются дополнительные очки, а противник выбывает из игры. При разрезании собственного шлейфа игрок выбывает из игры; + +4. **Столкновение с другим игроком** - при столкновении с другим игроком, проигрывает тот игрок, чей шлейф длиннее, при совпадении длин шлейфов, проигрывают оба игрока; + +5. **Взятие бонуса** - возможно двумя способами: 1) при прохождении по клетке бонуса 2) при захвате клетки, на которой есть бонус. Если бонус появился на территории игрока, то он не будет применен, и для его взятия необходимо пройти по клетке с ним; + +6. **Пересечение границы** - приводит к выбыванию из игры. + + +### 1.3. Условия победы + +Игра продолжается либо до смерти всех игроков, либо до максимального числа тиков (параметр конфигурации MAX_TICK_COUNT). + +Очки присваиваются за определенные игровые действия: +* Захват одной нейтральной клетки - 1 балл; +* Захват одной клетки противника - 5 баллов; +* Отрезание пилой территории противника - 30 баллов; +* Пересечение шлейфа противника - 50 баллов; +* Убийство противника пилой - 150 баллов; + +Причины выбывания игрока из игры: +* При пересечении шлейфа другими игроками или при самопересечении своего шлейфа; +* При столкновении с другим игроком, проигрывает тот игрок, чей шлейф длиннее, при совпадении длины шлейфа, проигрывают оба игрока; +* При пересечении границ карты; +* При захвате противниками всей вашей территории; + +Побеждает игрок, набравший наибольшее количество очков. + + +## 2. Создание решения + +Решение пользователя - это программа-бот, которая взаимодействует с сервером через потоки стандартного ввода-вывода. Она может быть написана на любом языке из списка поддерживаемых. Для некоторых языков будут доступны стандартная библиотека, библиотека для разбора JSON, и дополнения, предложенные участниками. + +Решение предоставляется участником на сайте соревнования как ZIP-архив, внутри которого обязательно есть файл с названием main, корректный для выбранного участником языка программирования. Последнее включает в себя: + +* корректное расширение (`.py` для Python, `.cpp` для С++ и т. д.); +* корректный синтаксис (к примеру, внутри С++ должна быть функция `main()`); +* корректные подключения других модулей/пакетов выбранного языка. + +После загрузки решение будет сначала скомпилировано (для компилируемых языков: C++, Go, Java, C#), а затем запущено в специальном docker-контейнере. Взаимодействие решения с внешним миром происходит происходит через потоки стандартного ввода/вывода. + +**Внимание!** Использование `stderr` является ошибкой. Кроме того, в файле стратегии должны отсутствовать логгеры, записывающие информацию в файл, иначе поведение вашей стратегии будет существенно отличаться от поведения на сервере. + +### 2.1. Архитектура решения +Самое простое решение на языке программирования Python 3 будет состоять из одного файла `main.py`. Программа-клиент инициирует сессию и запускает этот скрипт в начале работы. Клиент скрывает внутри себя работу с сетью и обработку ошибок, и является посредником между игровой механикой и решением участника. Он передает JSON от мира в стандартный поток ввода (`stdin`) и забирает ответ из стандартного потока вывода (`stdout`) (для Python это `sys.stdin` и `sys.stdout`). Напомним, что для корректной работы решение должно запускаться с параметрами для ввода/вывода без буфера (для Python это флаг `-u` при запуске интерпретатора). + +Максимальный размер файла стратегии — `20` Мб. + +Решение стартует один раз и далее работает в бесконечном цикле. Обрабатывать остановку не обязательно, в конце игры `dockerd` завершит работу всех контейнеров. Внутри цикла заключается обработка ввода/вывода, происходит обработка JSON и вызов главного обработчика (пример простой стратегии на Python): + +```python +import json +import random + +while True: + z = input() # получение ответа от сервера + commands = ['left', 'right', 'up', 'down'] # доступные команды + cmd = random.choice(commands) # случайный выбор действия + print(json.dumps({"command": cmd, 'debug': cmd}) # отправка результата +``` + +После старта игры, сервер-механика рассылает всем подключившимся решениям конфигурацию игры. В неё входят следующие параметры: +* `type` — `start_game` +* `params` — параметры игрового мира + * `x_cells_count` — количество элементарных ячеек по оси x + * `y_cells_count` — количество элементарных ячеек по оси y + * `speed` — скорость игрока + * `width` — ширина и высота элементарной ячейки + +Формат тика: +* `type` — `tick` +* `params` — параметры игрового мира + * `players` — словарь, где ключ - идентификатор игрока, а значение - его состояние. Ключ - это либо id противника (случайное и постоянное для данной игры число), либо строка 'i' - идентификатор данного игрока. Формат состояния: + * `score` — количество очков игрока + * `territory` — массив координат клеток, принадлежащих территории игрока + * `position` — текущее положение игрока + * `lines` — массив координат клеток шлейфа + * `bonuses` — массив активных бонусов игрока + * `type` — тип бонуса ('n' - Ускорение (Нитро), 's' - Замедление, 'saw' - Пила) + * `ticks` — сколько еще клеток будет активен бонус + * `bonuses` — массив бонусов, расположенных на карте + * `type` — тип бонуса ('n' - Ускорение (Нитро), 's' - Замедление, 'saw' - пила) + * `position` — координаты бонуса, массив из двух элементов - (x, y) + * `tick_num` — номер тика + +Ответ на один тик не должен превышать `REQUEST_MAX_TIME = 5` секунд. Суммарное время ответов на всю игру не должно превышать `MAX_EXECUTION_TIME = 120` секунд. + +Сообщение об окончании игры: +* `type` — `end_game` +* `params` — {} + + +### 3. Особенности запуска Local Runner + +Local Runner написан на языке программирования Python версии 3.6. Для запуска потребуется интерпретатор Python версии 3.6 или новее. Local Runner нужен для демонстрации и тестирования механики игры, при помощи него игроки могут пробовать свои решения на собственном компьютере. Аналогичный код работает у нас на серверах для запуска игровых сессий. + +Интерфейс программы можно увидеть на рисунке ниже: + +![Local Runner](images/localrunner.png) + +В папке `examples` можно найти примеры простых стратегий на различных языках программирования. Их поведение определяется случайным выбором одной из доступных команд. Запустить Local Runner можно командой `python localrunner.py` при условии, что установлены все зависимости из `requirements.txt`. + +Аргументы, которые можно передать в Local Runner: +* `-h` или `--help` — показать справку +* `-p1` или `--player1` — **команда для запуска исполняемого файла стратегии игрока 1 (аналогично для остальных игроков, к примеру -p3 и --player3)**, максимальное количество игроков - 6. Для игры с клавиатуры, можно в качестве значения передать строку 'keyboard'. Для запуска простой стратегии-заглушки можно передать строку 'simple_bot'. +* `--p1l` — путь к логам игрока 1 (аналогично для остальных игроков, к примеру -p3l) +* `-t` или `--timeout` — по умолчанию игра прекращается после количества тиков, равного MAX_TICK_COUNT. Чтобы отключить данное поведение, передайте ранеру аругмент -t off. + +Примеры запуска при установленном python3 вместе с зависимостями: +* ```$ python3 localrunner.py``` — запуск игры одним игроком с управлением с клавиатуры; +* ```$ python3 localrunner.py -p1 "python strategy.py"``` — запуск с явным указанием первого решения на python, лежащего в той же папке; +* ```$ python3 localrunner.py -p1 "java -jar /maic3/strategy.jar"``` — запуск решения на Java, лежащего по абсолютному пути в условной папке `/maic3/`; +* ```$ python3 localrunner.py -p1 "java -jar ../strategy_1/solution.jar" -p2 "../strategy_2/solution.exe"``` — запуск двух стратегий по относительному пути в папке на один уровень вверх; +* ```$ python3 localrunner.py -p1 "python strategy.py" -p2 simple_bot``` — запуск решения игрока вместе с простой стратегией; +* ```$ python3 localrunner.py -p1 "python strategy.py" -p2 keyboard``` — игра с клавиатуры вместе со своим решением; +* ```$ python3 localrunner.py -p1 "node strategy.js" -p2 simple_bot -p3 simple_bot -p4 simple_bot -p5 simple_bot -p6 simple_bot``` — запуск решения игрока вместе с 5ю простыми стратегиями-заглушками; + +**Внимание!** Под командой запуска подразумевается полная команда, используемая для исполнения решения-стратегии. Частой ошибкой участников является попытка использовать, например, `main.py` вместо `python main.py` или `main.cs` вместо пути к скомпилированному бинарному файлу. + +**Важно!** Не стоит забывать про буферизацию ввода/вывода в используемом инструментарии (например, интерпретатору python нужно передать флаг `-u` или выставить соответствующую переменную окружения). Без этого корректная работа не гарантируется. + +Пользователи Windows могут столкнуться с проблемой, когда интерпретатор языка программирования установлен не совсем верно, и его нет в системной переменной `PATH`. В таком случае необходимо указывать полный путь к интерпретатору вашего языка или добавлять его в `PATH`. + +## Спасибо, что участвуете в наших чемпионатах! diff --git a/paperio/dockers/base/Dockerfile b/paperio/dockers/base/Dockerfile new file mode 100644 index 0000000..74ec70b --- /dev/null +++ b/paperio/dockers/base/Dockerfile @@ -0,0 +1,18 @@ +FROM ubuntu:16.04 +MAINTAINER Kislenko Maksim + +ENV MOUNT_POINT=/opt/client/code +ENV SOLUTION_CODE_PATH=/opt/client/solution + +RUN apt-get update && \ + apt-get install -y unzip curl software-properties-common language-pack-en-base build-essential qt5-default python && \ + apt-get clean && \ + apt-get autoclean && \ + apt-get autoremove + +WORKDIR /opt/client +COPY ./sources ./ +RUN qmake ./client.pro -r CONFIG+=x86_64 && make && rm -f Makefile client.pro constants.h main.cpp main.o moc_tcp_client.cpp moc_tcp_client.o tcp_client.h +RUN mkdir /opt/client/solution && chmod 777 /opt/client/solution + +CMD ["bash", "run.sh"] diff --git a/paperio/dockers/base/sources/client.pro b/paperio/dockers/base/sources/client.pro new file mode 100644 index 0000000..51ea8d0 --- /dev/null +++ b/paperio/dockers/base/sources/client.pro @@ -0,0 +1,20 @@ +QT += core network +QT -= gui + +CONFIG += c++11 warn_off + +TARGET = client +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +SOURCES += main.cpp + +DISTFILES += \ + ../Dockerfile \ + run.sh + +HEADERS += \ + tcp_client.h \ + constants.h diff --git a/paperio/dockers/base/sources/compile_output_processor.py b/paperio/dockers/base/sources/compile_output_processor.py new file mode 100644 index 0000000..248d15d --- /dev/null +++ b/paperio/dockers/base/sources/compile_output_processor.py @@ -0,0 +1,11 @@ +import sys +import json +data = sys.stdin.readlines() +strings = [] +for s in data: + try: + string = unicode(s) + except UnicodeDecodeError: + string = "Can't parse string\n" + strings.append(string) +print json.dumps(''.join(strings)[:100000]) \ No newline at end of file diff --git a/paperio/dockers/base/sources/constants.h b/paperio/dockers/base/sources/constants.h new file mode 100644 index 0000000..caa8dae --- /dev/null +++ b/paperio/dockers/base/sources/constants.h @@ -0,0 +1,13 @@ +#ifndef CONSTANTS_H +#define CONSTANTS_H + +#include +#include +#include +#include + +const int PORT = 8000; +const int MAX_RESP_LEN = 10000; + + +#endif // CONSTANTS_H diff --git a/paperio/dockers/base/sources/main.cpp b/paperio/dockers/base/sources/main.cpp new file mode 100644 index 0000000..5a7ab57 --- /dev/null +++ b/paperio/dockers/base/sources/main.cpp @@ -0,0 +1,85 @@ +#include "tcp_client.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +int lookup_host(const char* host, char* addrstr) { + struct addrinfo hints, *res; + int errcode; + void *ptr; + + memset(&hints, 0, sizeof (hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags |= AI_CANONNAME; + + errcode = getaddrinfo(host, NULL, &hints, &res); + if (errcode != 0) { + return -1; + } + printf("Host: %s\n", host); + while (res) { + inet_ntop(res->ai_family, res->ai_addr->sa_data, addrstr, 100); + + switch (res->ai_family) { + case AF_INET: + ptr = &((struct sockaddr_in *) res->ai_addr)->sin_addr; + break; + case AF_INET6: + ptr = &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr; + break; + } + inet_ntop(res->ai_family, ptr, addrstr, 100); + printf("IPv%d address: %s (%s)\n", res->ai_family == PF_INET6 ? 6 : 4, addrstr, res->ai_canonname); + res = res->ai_next; + } + return 0; +} + + +int main(int argc, char *argv[]) { + if (argc < 3) { + qDebug() << "Usage: ./client "; + return 0; + } + QString exec_path = argv[1]; + QString work_dir = argv[2]; + + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + QString solution_id = env.value("SOLUTION_ID"); + if (solution_id == "") { + qDebug() << "solution_id must be specified!"; + return 0; + } + + QString HOST = "127.0.0.1"; + QString env_value = env.value("WORLD_NAME"); + char host_ip[100]; + if (lookup_host(env_value.toStdString().c_str(), host_ip) == 0) { + HOST = QString(host_ip); + } + else { + qDebug() << "Can't determine host"; + } + int PORT = 8000; + + QCoreApplication a(argc, argv); + TcpClient client(HOST, PORT, solution_id.toInt(), exec_path, work_dir); + qDebug() << "Sleeping (5 secs)..."; + sleep(5); + + qDebug() << "Starting client..."; + client.start(); + + QObject::connect(&client, SIGNAL(finished()), &a,SLOT(quit())); + return a.exec(); +} diff --git a/paperio/dockers/base/sources/run.sh b/paperio/dockers/base/sources/run.sh new file mode 100644 index 0000000..c2743dc --- /dev/null +++ b/paperio/dockers/base/sources/run.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +COP_PATH="$(pwd)/compile_output_processor.py" + +if [ "$ZIPPED" = True ]; then + yes | unzip -n $MOUNT_POINT -d $SOLUTION_CODE_PATH +else + yes | cp $MOUNT_POINT $SOLUTION_CODE_PATH/$SOLUTION_CODE_ENTRYPOINT +fi + +if [ "$COMPILE" = True ]; then + STATUS="ok" + MESSAGE="compilation done" + FILE_PATH=$COMPILED_FILE_PATH + + ERRORS="$(eval $COMPILATION_COMMAND)" + + if [ $? -ne 0 ]; then + STATUS="error" + FILE_PATH="" + MESSAGE="$ERRORS" + fi + echo "{\"status\": \"$STATUS\",\"message\": `echo "$MESSAGE" | python $COP_PATH`,\"path_to_compiled_file\": \"$FILE_PATH\"}" > $COMPILE_LOG_LOCATION + +else + ./client "$(eval echo $RUN_COMMAND)" $SOLUTION_CODE_PATH +fi diff --git a/paperio/dockers/base/sources/tcp_client.h b/paperio/dockers/base/sources/tcp_client.h new file mode 100644 index 0000000..2c3cd49 --- /dev/null +++ b/paperio/dockers/base/sources/tcp_client.h @@ -0,0 +1,135 @@ +#ifndef TCP_CLIENT_H +#define TCP_CLIENT_H + +#include "constants.h" +#include + +#include +#include +#include +#include +#include +#include + + +class TcpClient : public QObject +{ + Q_OBJECT + +private: + QTcpSocket *socket; + QString host; + int port; + int solution_id; + bool first_read; + + QString solution_run; + QProcess *solution; + +signals: + void finished(); + +public: + TcpClient(const QString& _host, int _port, int _solution_id, QString exec, QString work_dir) : + host(_host), + port(_port), + solution_id(_solution_id), + first_read(true) + { + socket = new QTcpSocket(this); + solution = new QProcess(this); + solution->setWorkingDirectory(work_dir); + + solution_run = exec; +// QFile run_file; +// run_file.setFileName(exec); +// if (run_file.open(QFile::ReadOnly)) { +// solution_run = QString(run_file.readAll()); +// } +// run_file.close(); + } + + virtual ~TcpClient() { + if (socket) delete socket; + if (solution) delete solution; + } + + void start() { + connect(socket, SIGNAL(connected()), SLOT(on_connected())); + connect(socket, SIGNAL(readyRead()), SLOT(on_ready_read())); + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(on_error(QAbstractSocket::SocketError))); + + connect(solution, SIGNAL(readyReadStandardOutput()), SLOT(on_solution_out())); + connect(solution, SIGNAL(readyReadStandardError()), SLOT(on_solution_error())); + connect(solution, SIGNAL(finished(int)), SLOT(on_solution_finished(int))); + socket->connectToHost(host, port); + } + +private slots: + void on_connected() { + QString greeting = "{\"solution_id\": \"" + QString::number(solution_id) + "\"}\n"; + int sent = socket->write(greeting.toStdString().c_str()); + if (sent == -1) { + qDebug() << "Can not send solution_id"; + } + } + + void on_ready_read() { + QByteArray &&data = socket->readLine(0); + qDebug() << data; +// qDebug() << "Data:" << data; + if (first_read) { + solution->start(solution_run); + first_read = false; + } + int written = solution->write(data); + // qDebug() << "Written" << written; + if (written == -1) { + qDebug() << "Can not write to solution"; + } + } + + void on_error(QAbstractSocket::SocketError error) { + qDebug() << "Socket error:" << error; + solution->close(); + socket->disconnectFromHost(); + emit finished(); + } + +public slots: + void on_solution_out() { + QByteArray &&cmd = solution->readLine(MAX_RESP_LEN); + // qDebug() << cmd; + + int sent = socket->write(cmd); + if (sent == -1) { + qDebug() << "Can not send command"; + } + } + + void on_solution_error() { + QByteArray error = solution->readAllStandardError(); + qDebug() << "Error:" << error; + + QJsonObject jsonError; + jsonError.insert("error", QJsonValue(QString(error))); + QJsonDocument jsonDoc(jsonError); + QString cmd = QString(jsonDoc.toJson(QJsonDocument::Compact)) + "\n"; + + int sent = socket->write(cmd.toStdString().c_str()); + if (sent == -1) { + qDebug() << "Can not send error"; + } + } + + void on_solution_finished(int code) { + qDebug() << "Solution finished: code" << code; + sleep(5); + socket->disconnectFromHost(); + emit finished(); + } +}; + + + +#endif // TCP_CLIENT_H diff --git a/paperio/dockers/c_sharp/Dockerfile b/paperio/dockers/c_sharp/Dockerfile new file mode 100644 index 0000000..15769ae --- /dev/null +++ b/paperio/dockers/c_sharp/Dockerfile @@ -0,0 +1,18 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Boris Kolganov + +ENV MONO_GC_PARAMS max-heap-size=256M + +ENV SOLUTION_CODE_ENTRYPOINT=main.cs +ENV COMPILED_FILE_PATH=/opt/client/csharpStrategy +ENV COMPILATION_COMMAND='csc /unsafe /reference:Newtonsoft.Json.dll `find $SOLUTION_CODE_PATH -name "*.cs"` -out:$COMPILED_FILE_PATH' +ENV RUN_COMMAND='mono $MOUNT_POINT' + +RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF +RUN echo "deb http://download.mono-project.com/repo/ubuntu stable-trusty main" > /etc/apt/sources.list.d/mono-official-stable.list && \ + apt-get update && \ + apt-get install -y mono-complete zip && \ + rm -rf /var/lib/apt/lists/* /tmp/* && \ + export MONO_GC_PARAMS=max-heap-size=256M + +COPY Newtonsoft.Json.dll ./ diff --git a/paperio/dockers/cpp11/Dockerfile b/paperio/dockers/cpp11/Dockerfile new file mode 100644 index 0000000..5b4ae2d --- /dev/null +++ b/paperio/dockers/cpp11/Dockerfile @@ -0,0 +1,10 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Boris Kolganov + +ENV COMPILED_FILE_PATH=/opt/client/a.out +ENV SOLUTION_CODE_ENTRYPOINT=main.cpp +ENV DEFALUT_COMPILATION_COMMAND='g++ -m64 -pipe -O2 -std=c++11 -pthread -w -o $COMPILED_FILE_PATH $SOLUTION_CODE_PATH/main.cpp 2>&1 > /dev/null' +ENV COMPILATION_COMMAND='if [ -f $SOLUTION_CODE_PATH/__build__.sh ]; then cd $SOLUTION_CODE_PATH; . $SOLUTION_CODE_PATH/__build__.sh; else eval "$DEFALUT_COMPILATION_COMMAND"; fi 2>&1 > /dev/null' +ENV RUN_COMMAND='/lib64/ld-linux-x86-64.so.2 $MOUNT_POINT' + +COPY ./nlohmann ./nlohmann diff --git a/paperio/dockers/cpp11/nlohmann/LICENSE.MIT b/paperio/dockers/cpp11/nlohmann/LICENSE.MIT new file mode 100644 index 0000000..8b0f700 --- /dev/null +++ b/paperio/dockers/cpp11/nlohmann/LICENSE.MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-2018 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. diff --git a/paperio/dockers/cpp11/nlohmann/json.hpp b/paperio/dockers/cpp11/nlohmann/json.hpp new file mode 100644 index 0000000..3dcb834 --- /dev/null +++ b/paperio/dockers/cpp11/nlohmann/json.hpp @@ -0,0 +1,17190 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 3.1.1 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +Copyright (c) 2013-2018 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. +*/ + +#ifndef NLOHMANN_JSON_HPP +#define NLOHMANN_JSON_HPP + +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 1 +#define NLOHMANN_JSON_VERSION_PATCH 1 + +#include // all_of, find, for_each +#include // assert +#include // and, not, or +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#include // istream, ostream +#include // iterator_traits, random_access_iterator_tag +#include // accumulate +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap + +// #include +#ifndef NLOHMANN_JSON_FWD_HPP +#define NLOHMANN_JSON_FWD_HPP + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string +#include // vector + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} + +#endif + +// #include + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// exclude unsupported compilers +#if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow for portable deprecation warnings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define JSON_DEPRECATED __declspec(deprecated) +#else + #define JSON_DEPRECATED +#endif + +// allow to disable exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) +#else + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER +#endif + +// manual branch prediction +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) + #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else + #define JSON_LIKELY(x) x + #define JSON_UNLIKELY(x) x +#endif + +// C++ language standard detection +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +/*! +@brief Helper to determine whether there's a key_type for T. + +This helper is used to tell associative containers apart from other containers +such as sequence containers. For instance, `std::map` passes the test as it +contains a `mapped_type`, whereas `std::vector` fails the test. + +@sa http://stackoverflow.com/a/7728728/266378 +@since version 1.0.0, overworked in version 2.0.6 +*/ +#define NLOHMANN_JSON_HAS_HELPER(type) \ + template struct has_##type { \ + private: \ + template \ + static int detect(U &&); \ + static void detect(...); \ + public: \ + static constexpr bool value = \ + std::is_integral()))>::value; \ + } + +// #include + + +#include // not +#include // size_t +#include // numeric_limits +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // declval + +// #include + +// #include + + +namespace nlohmann +{ +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ +///////////// +// helpers // +///////////// + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +/* +Implementation of two C++17 constructs: conjunction, negation. This is needed +to avoid evaluating all the traits in a condition + +For example: not std::is_same::value and has_value_type::value +will not compile when T = void (on MSVC at least). Whereas +conjunction>, has_value_type>::value will +stop evaluating if negation<...>::value == false + +Please note that those constructs must be used with caution, since symbols can +become very long quickly (which can slow down compilation and cause MSVC +internal compiler errors). Only use it when you have to (see example ahead). +*/ +template struct conjunction : std::true_type {}; +template struct conjunction : B1 {}; +template +struct conjunction : std::conditional, B1>::type {}; + +template struct negation : std::integral_constant {}; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +//////////////////////// +// has_/is_ functions // +//////////////////////// + +// source: https://stackoverflow.com/a/37193089/4116453 + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +NLOHMANN_JSON_HAS_HELPER(mapped_type); +NLOHMANN_JSON_HAS_HELPER(key_type); +NLOHMANN_JSON_HAS_HELPER(value_type); +NLOHMANN_JSON_HAS_HELPER(iterator); + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl +{ + static constexpr auto value = + std::is_constructible::value and + std::is_constructible::value; +}; + +template +struct is_compatible_object_type +{ + static auto constexpr value = is_compatible_object_type_impl < + conjunction>, + has_mapped_type, + has_key_type>::value, + typename BasicJsonType::object_t, CompatibleObjectType >::value; +}; + +template +struct is_basic_json_nested_type +{ + static auto constexpr value = std::is_same::value or + std::is_same::value or + std::is_same::value or + std::is_same::value; +}; + +template +struct is_compatible_array_type +{ + static auto constexpr value = + conjunction>, + negation>, + negation>, + negation>, + has_value_type, + has_iterator>::value; +}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + std::is_constructible::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type +{ + static constexpr auto value = + is_compatible_integer_type_impl < + std::is_integral::value and + not std::is_same::value, + RealIntegerType, CompatibleNumberIntegerType > ::value; +}; + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json +{ + private: + // also check the return type of from_json + template::from_json( + std::declval(), std::declval()))>::value>> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json +{ + private: + template < + typename U, + typename = enable_if_t::from_json(std::declval()))>::value >> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +template +struct has_to_json +{ + private: + template::to_json( + std::declval(), std::declval()))> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +template +struct is_compatible_complete_type +{ + static constexpr bool value = + not std::is_base_of::value and + not std::is_same::value and + not is_basic_json_nested_type::value and + has_to_json::value; +}; + +template +struct is_compatible_type + : conjunction, + is_compatible_complete_type> +{ +}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} +} + +// #include + + +#include // exception +#include // runtime_error +#include // to_string + +namespace nlohmann +{ +namespace detail +{ +//////////////// +// exceptions // +//////////////// + +/*! +@brief general exception of the @ref basic_json class + +This class is an extension of `std::exception` objects with a member @a id for +exception ids. It is used as the base class for all exceptions thrown by the +@ref basic_json class. This class can hence be used as "wildcard" to catch +exceptions. + +Subclasses: +- @ref parse_error for exceptions indicating a parse error +- @ref invalid_iterator for exceptions indicating errors with iterators +- @ref type_error for exceptions indicating executing a member function with + a wrong type +- @ref out_of_range for exceptions indicating access out of the defined range +- @ref other_error for exceptions indicating other library errors + +@internal +@note To have nothrow-copy-constructible exceptions, we internally use + `std::runtime_error` which can cope with arbitrary-length error messages. + Intermediate strings are built with static functions and then passed to + the actual constructor. +@endinternal + +@liveexample{The following code shows how arbitrary library exceptions can be +caught.,exception} + +@since version 3.0.0 +*/ +class exception : public std::exception +{ + public: + /// returns the explanatory string + const char* what() const noexcept override + { + return m.what(); + } + + /// the id of the exception + const int id; + + protected: + exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} + + static std::string name(const std::string& ename, int id_) + { + return "[json.exception." + ename + "." + std::to_string(id_) + "] "; + } + + private: + /// an exception object as storage for error messages + std::runtime_error m; +}; + +/*! +@brief exception indicating a parse error + +This exception is thrown by the library when a parse error occurs. Parse errors +can occur during the deserialization of JSON text, CBOR, MessagePack, as well +as when using JSON Patch. + +Member @a byte holds the byte index of the last read character in the input +file. + +Exceptions have ids 1xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. +json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. +json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. +json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. +json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. +json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. +json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. +json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. +json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. +json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. +json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. + +@note For an input with n bytes, 1 is the index of the first character and n+1 + is the index of the terminating null byte or the end of file. This also + holds true when reading a byte vector (CBOR or MessagePack). + +@liveexample{The following code shows how a `parse_error` exception can be +caught.,parse_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class parse_error : public exception +{ + public: + /*! + @brief create a parse error exception + @param[in] id_ the id of the exception + @param[in] byte_ the byte index where the error occurred (or 0 if the + position cannot be determined) + @param[in] what_arg the explanatory string + @return parse_error object + */ + static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + (byte_ != 0 ? (" at " + std::to_string(byte_)) : "") + + ": " + what_arg; + return parse_error(id_, byte_, w.c_str()); + } + + /*! + @brief byte index of the parse error + + The byte index of the last read character in the input file. + + @note For an input with n bytes, 1 is the index of the first character and + n+1 is the index of the terminating null byte or the end of file. + This also holds true when reading a byte vector (CBOR or MessagePack). + */ + const std::size_t byte; + + private: + parse_error(int id_, std::size_t byte_, const char* what_arg) + : exception(id_, what_arg), byte(byte_) {} +}; + +/*! +@brief exception indicating errors with iterators + +This exception is thrown if iterators passed to a library function do not match +the expected semantics. + +Exceptions have ids 2xx. + +name / id | example message | description +----------------------------------- | --------------- | ------------------------- +json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. +json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. +json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. +json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. +json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. +json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. +json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. +json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. +json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. +json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). + +@liveexample{The following code shows how an `invalid_iterator` exception can be +caught.,invalid_iterator} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class invalid_iterator : public exception +{ + public: + static invalid_iterator create(int id_, const std::string& what_arg) + { + std::string w = exception::name("invalid_iterator", id_) + what_arg; + return invalid_iterator(id_, w.c_str()); + } + + private: + invalid_iterator(int id_, const char* what_arg) + : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating executing a member function with a wrong type + +This exception is thrown in case of a type error; that is, a library function is +executed on a JSON value whose type does not match the expected semantics. + +Exceptions have ids 3xx. + +name / id | example message | description +----------------------------- | --------------- | ------------------------- +json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. +json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. +json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t&. +json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. +json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. +json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. +json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. +json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. +json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. +json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. +json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. +json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. +json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. +json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. +json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. +json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | + +@liveexample{The following code shows how a `type_error` exception can be +caught.,type_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class type_error : public exception +{ + public: + static type_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("type_error", id_) + what_arg; + return type_error(id_, w.c_str()); + } + + private: + type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating access out of the defined range + +This exception is thrown in case a library function is called on an input +parameter that exceeds the expected range, for instance in case of array +indices or nonexisting object keys. + +Exceptions have ids 4xx. + +name / id | example message | description +------------------------------- | --------------- | ------------------------- +json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. +json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. +json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. +json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. +json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. +json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. +json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON only supports integers numbers up to 9223372036854775807. | +json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | + +@liveexample{The following code shows how an `out_of_range` exception can be +caught.,out_of_range} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class out_of_range : public exception +{ + public: + static out_of_range create(int id_, const std::string& what_arg) + { + std::string w = exception::name("out_of_range", id_) + what_arg; + return out_of_range(id_, w.c_str()); + } + + private: + out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating other library errors + +This exception is thrown in case of errors that cannot be classified with the +other exception types. + +Exceptions have ids 5xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range + +@liveexample{The following code shows how an `other_error` exception can be +caught.,other_error} + +@since version 3.0.0 +*/ +class other_error : public exception +{ + public: + static other_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("other_error", id_) + what_arg; + return other_error(id_, w.c_str()); + } + + private: + other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; +} +} + +// #include + + +#include // array +#include // and +#include // size_t +#include // uint8_t + +namespace nlohmann +{ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + discarded ///< discarded by the the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string +- furthermore, each type is not smaller than itself +- discarded values are not comparable + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); + return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index]; +} +} +} + +// #include + + +#include // transform +#include // array +#include // and, not +#include // forward_list +#include // inserter, front_inserter, end +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // pair, declval +#include // valarray + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// overloads for basic_json template parameters +template::value and + not std::is_same::value, + int> = 0> +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (JSON_UNLIKELY(not j.is_boolean())) + { + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); + } + b = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (JSON_UNLIKELY(not j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); + } + s = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, EnumType& e) +{ + typename std::underlying_type::type val; + get_arithmetic_value(j, val); + e = static_cast(val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + arr = *j.template get_ptr(); +} + +// forward_list doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::forward_list& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + std::transform(j.rbegin(), j.rend(), + std::front_inserter(l), [](const BasicJsonType & i) + { + return i.template get(); + }); +} + +// valarray doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::valarray& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + l.resize(j.size()); + std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l)); +} + +template +void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0> /*unused*/) +{ + using std::end; + + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) +-> decltype( + arr.reserve(std::declval()), + void()) +{ + using std::end; + + arr.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +void from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get(); + } +} + +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < + is_compatible_array_type::value and + not std::is_same::value and + std::is_constructible < + BasicJsonType, typename CompatibleArrayType::value_type >::value, + int > = 0 > +void from_json(const BasicJsonType& j, CompatibleArrayType& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + + std::string(j.type_name()))); + } + + from_json_array_impl(j, arr, priority_tag<2> {}); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +{ + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); + } + + auto inner_object = j.template get_ptr(); + using value_type = typename CompatibleObjectType::value_type; + std::transform( + inner_object->begin(), inner_object->end(), + std::inserter(obj, obj.begin()), + [](typename BasicJsonType::object_t::value_type const & p) + { + return value_type(p.first, p.second.template get()); + }); +} + +// overload for arithmetic types, not chosen for basic_json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for boolean_t etc. in case of a custom BooleanType which is not +// an arithmetic type? +template::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value, + int> = 0> +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::boolean: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, std::pair& p) +{ + p = {j.at(0).template get(), j.at(1).template get()}; +} + +template +void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence) +{ + t = std::make_tuple(j.at(Idx).template get::type>()...); +} + +template +void from_json(const BasicJsonType& j, std::tuple& t) +{ + from_json_tuple_impl(j, t, index_sequence_for {}); +} + +struct from_json_fn +{ + private: + template + auto call(const BasicJsonType& j, T& val, priority_tag<1> /*unused*/) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } + + template + void call(const BasicJsonType& /*unused*/, T& /*unused*/, priority_tag<0> /*unused*/) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find from_json() method in T's namespace"); +#ifdef _MSC_VER + // MSVC does not show a stacktrace for the above assert + using decayed = uncvref_t; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif + } + + public: + template + void operator()(const BasicJsonType& j, T& val) const + noexcept(noexcept(std::declval().call(j, val, priority_tag<1> {}))) + { + return call(j, val, priority_tag<1> {}); + } +}; +} + +/// namespace to hold default `from_json` function +/// to see why this is required: +/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html +namespace +{ +constexpr const auto& from_json = detail::static_const::value; +} +} + +// #include + + +#include // or, and, not +#include // begin, end +#include // tuple, get +#include // is_same, is_constructible, is_floating_point, is_enum, underlying_type +#include // move, forward, declval, pair +#include // valarray +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +////////////////// +// constructors // +////////////////// + +template struct external_constructor; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept + { + j.m_type = value_t::boolean; + j.m_value = b; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) + { + j.m_type = value_t::string; + j.m_value = s; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) + { + j.m_type = value_t::string; + j.m_value = std::move(s); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept + { + j.m_type = value_t::number_float; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept + { + j.m_type = value_t::number_unsigned; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept + { + j.m_type = value_t::number_integer; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) + { + j.m_type = value_t::array; + j.m_value = std::move(arr); + j.assert_invariant(); + } + + template::value, + int> = 0> + static void construct(BasicJsonType& j, const CompatibleArrayType& arr) + { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = j.template create(begin(arr), end(arr)); + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, const std::vector& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->reserve(arr.size()); + for (const bool x : arr) + { + j.m_value.array->push_back(x); + } + j.assert_invariant(); + } + + template::value, int> = 0> + static void construct(BasicJsonType& j, const std::valarray& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->resize(arr.size()); + std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) + { + j.m_type = value_t::object; + j.m_value = obj; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) + { + j.m_type = value_t::object; + j.m_value = std::move(obj); + j.assert_invariant(); + } + + template::value, int> = 0> + static void construct(BasicJsonType& j, const CompatibleObjectType& obj) + { + using std::begin; + using std::end; + + j.m_type = value_t::object; + j.m_value.object = j.template create(begin(obj), end(obj)); + j.assert_invariant(); + } +}; + +///////////// +// to_json // +///////////// + +template::value, int> = 0> +void to_json(BasicJsonType& j, T b) noexcept +{ + external_constructor::construct(j, b); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleString& s) +{ + external_constructor::construct(j, s); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +{ + external_constructor::construct(j, std::move(s)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, FloatType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, EnumType e) noexcept +{ + using underlying_type = typename std::underlying_type::type; + external_constructor::construct(j, static_cast(e)); +} + +template +void to_json(BasicJsonType& j, const std::vector& e) +{ + external_constructor::construct(j, e); +} + +template::value or + std::is_same::value, + int> = 0> +void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +{ + external_constructor::construct(j, arr); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, std::valarray arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +{ + external_constructor::construct(j, obj); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +{ + external_constructor::construct(j, std::move(obj)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, T (&arr)[N]) +{ + external_constructor::construct(j, arr); +} + +template +void to_json(BasicJsonType& j, const std::pair& p) +{ + j = {p.first, p.second}; +} + +template +void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence) +{ + j = {std::get(t)...}; +} + +template +void to_json(BasicJsonType& j, const std::tuple& t) +{ + to_json_tuple_impl(j, t, index_sequence_for {}); +} + +struct to_json_fn +{ + private: + template + auto call(BasicJsonType& j, T&& val, priority_tag<1> /*unused*/) const noexcept(noexcept(to_json(j, std::forward(val)))) + -> decltype(to_json(j, std::forward(val)), void()) + { + return to_json(j, std::forward(val)); + } + + template + void call(BasicJsonType& /*unused*/, T&& /*unused*/, priority_tag<0> /*unused*/) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find to_json() method in T's namespace"); + +#ifdef _MSC_VER + // MSVC does not show a stacktrace for the above assert + using decayed = uncvref_t; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif + } + + public: + template + void operator()(BasicJsonType& j, T&& val) const + noexcept(noexcept(std::declval().call(j, std::forward(val), priority_tag<1> {}))) + { + return call(j, std::forward(val), priority_tag<1> {}); + } +}; +} + +/// namespace to hold default `to_json` function +namespace +{ +constexpr const auto& to_json = detail::static_const::value; +} +} + +// #include + + +#include // min +#include // array +#include // assert +#include // size_t +#include // strlen +#include // streamsize, streamoff, streampos +#include // istream +#include // begin, end, iterator_traits, random_access_iterator_tag, distance, next +#include // shared_ptr, make_shared, addressof +#include // accumulate +#include // string, char_traits +#include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer +#include // pair, declval + +// #include + + +namespace nlohmann +{ +namespace detail +{ +//////////////////// +// input adapters // +//////////////////// + +/*! +@brief abstract input adapter interface + +Produces a stream of std::char_traits::int_type characters from a +std::istream, a buffer, or some other input type. Accepts the return of exactly +one non-EOF character for future input. The int_type characters returned +consist of all valid char values as positive values (typically unsigned char), +plus an EOF value outside that range, specified by the value of the function +std::char_traits::eof(). This value is typically -1, but could be any +arbitrary value which is not a valid char value. +*/ +struct input_adapter_protocol +{ + /// get a character [0,255] or std::char_traits::eof(). + virtual std::char_traits::int_type get_character() = 0; + /// restore the last non-eof() character to input + virtual void unget_character() = 0; + virtual ~input_adapter_protocol() = default; +}; + +/// a type to simplify interfaces +using input_adapter_t = std::shared_ptr; + +/*! +Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at +beginning of input. Does not support changing the underlying std::streambuf +in mid-input. Maintains underlying std::istream and std::streambuf to support +subsequent use of standard std::istream operations to process any input +characters following those used in parsing the JSON input. Clears the +std::istream flags; any input errors (e.g., EOF) will be detected by the first +subsequent call for input from the std::istream. +*/ +class input_stream_adapter : public input_adapter_protocol +{ + public: + ~input_stream_adapter() override + { + // clear stream flags; we use underlying streambuf I/O, do not + // maintain ifstream flags + is.clear(); + } + + explicit input_stream_adapter(std::istream& i) + : is(i), sb(*i.rdbuf()) + { + // skip byte order mark + std::char_traits::int_type c; + if ((c = get_character()) == 0xEF) + { + if ((c = get_character()) == 0xBB) + { + if ((c = get_character()) == 0xBF) + { + return; // Ignore BOM + } + else if (c != std::char_traits::eof()) + { + is.unget(); + } + is.putback('\xBB'); + } + else if (c != std::char_traits::eof()) + { + is.unget(); + } + is.putback('\xEF'); + } + else if (c != std::char_traits::eof()) + { + is.unget(); // no byte order mark; process as usual + } + } + + // delete because of pointer members + input_stream_adapter(const input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&) = delete; + + // std::istream/std::streambuf use std::char_traits::to_int_type, to + // ensure that std::char_traits::eof() and the character 0xFF do not + // end up as the same value, eg. 0xFFFFFFFF. + std::char_traits::int_type get_character() override + { + return sb.sbumpc(); + } + + void unget_character() override + { + sb.sungetc(); // is.unget() avoided for performance + } + + private: + /// the associated input stream + std::istream& is; + std::streambuf& sb; +}; + +/// input adapter for buffer input +class input_buffer_adapter : public input_adapter_protocol +{ + public: + input_buffer_adapter(const char* b, const std::size_t l) + : cursor(b), limit(b + l), start(b) + { + // skip byte order mark + if (l >= 3 and b[0] == '\xEF' and b[1] == '\xBB' and b[2] == '\xBF') + { + cursor += 3; + } + } + + // delete because of pointer members + input_buffer_adapter(const input_buffer_adapter&) = delete; + input_buffer_adapter& operator=(input_buffer_adapter&) = delete; + + std::char_traits::int_type get_character() noexcept override + { + if (JSON_LIKELY(cursor < limit)) + { + return std::char_traits::to_int_type(*(cursor++)); + } + + return std::char_traits::eof(); + } + + void unget_character() noexcept override + { + if (JSON_LIKELY(cursor > start)) + { + --cursor; + } + } + + private: + /// pointer to the current character + const char* cursor; + /// pointer past the last character + const char* limit; + /// pointer to the first character + const char* start; +}; + +class input_adapter +{ + public: + // native support + + /// input adapter for input stream + input_adapter(std::istream& i) + : ia(std::make_shared(i)) {} + + /// input adapter for input stream + input_adapter(std::istream&& i) + : ia(std::make_shared(i)) {} + + /// input adapter for buffer + template::value and + std::is_integral::type>::value and + sizeof(typename std::remove_pointer::type) == 1, + int>::type = 0> + input_adapter(CharT b, std::size_t l) + : ia(std::make_shared(reinterpret_cast(b), l)) {} + + // derived support + + /// input adapter for string literal + template::value and + std::is_integral::type>::value and + sizeof(typename std::remove_pointer::type) == 1, + int>::type = 0> + input_adapter(CharT b) + : input_adapter(reinterpret_cast(b), + std::strlen(reinterpret_cast(b))) {} + + /// input adapter for iterator range with contiguous storage + template::iterator_category, std::random_access_iterator_tag>::value, + int>::type = 0> + input_adapter(IteratorType first, IteratorType last) + { + // assertion to check that the iterator range is indeed contiguous, + // see http://stackoverflow.com/a/35008842/266378 for more discussion + assert(std::accumulate( + first, last, std::pair(true, 0), + [&first](std::pair res, decltype(*first) val) + { + res.first &= (val == *(std::next(std::addressof(*first), res.second++))); + return res; + }).first); + + // assertion to check that each element is 1 byte long + static_assert( + sizeof(typename std::iterator_traits::value_type) == 1, + "each element in the iterator range must have the size of 1 byte"); + + const auto len = static_cast(std::distance(first, last)); + if (JSON_LIKELY(len > 0)) + { + // there is at least one element: use the address of first + ia = std::make_shared(reinterpret_cast(&(*first)), len); + } + else + { + // the address of first cannot be used: use nullptr + ia = std::make_shared(nullptr, len); + } + } + + /// input adapter for array + template + input_adapter(T (&array)[N]) + : input_adapter(std::begin(array), std::end(array)) {} + + /// input adapter for contiguous container + template::value and + std::is_base_of()))>::iterator_category>::value, + int>::type = 0> + input_adapter(const ContiguousContainer& c) + : input_adapter(std::begin(c), std::end(c)) {} + + operator input_adapter_t() + { + return ia; + } + + private: + /// the actual adapter + input_adapter_t ia = nullptr; +}; +} +} + +// #include + + +#include // localeconv +#include // size_t +#include // strtof, strtod, strtold, strtoll, strtoull +#include // initializer_list +#include // hex, uppercase +#include // setw, setfill +#include // stringstream +#include // char_traits, string +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////// +// lexer // +/////////// + +/*! +@brief lexical analysis + +This class organizes the lexical analysis during JSON deserialization. +*/ +template +class lexer +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value + value_integer, ///< a signed integer -- use get_number_integer() for actual value + value_float, ///< an floating point number -- use get_number_float() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input, ///< indicating the end of the input buffer + literal_or_value ///< a literal or the begin of a value (only for diagnostics) + }; + + /// return name of values of type token_type (only used for errors) + static const char* token_type_name(const token_type t) noexcept + { + switch (t) + { + case token_type::uninitialized: + return ""; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case lexer::token_type::value_unsigned: + case lexer::token_type::value_integer: + case lexer::token_type::value_float: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return ""; + case token_type::end_of_input: + return "end of input"; + case token_type::literal_or_value: + return "'[', '{', or a literal"; + default: // catch non-enum values + return "unknown token"; // LCOV_EXCL_LINE + } + } + + explicit lexer(detail::input_adapter_t adapter) + : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {} + + // delete because of pointer members + lexer(const lexer&) = delete; + lexer& operator=(lexer&) = delete; + + private: + ///////////////////// + // locales + ///////////////////// + + /// return the locale-dependent decimal point + static char get_decimal_point() noexcept + { + const auto loc = localeconv(); + assert(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); + } + + ///////////////////// + // scan functions + ///////////////////// + + /*! + @brief get codepoint from 4 hex characters following `\u` + + For input "\u c1 c2 c3 c4" the codepoint is: + (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 + = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) + + Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' + must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The + conversion is done by subtracting the offset (0x30, 0x37, and 0x57) + between the ASCII value of the character and the desired integer value. + + @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or + non-hex character) + */ + int get_codepoint() + { + // this function only makes sense after reading `\u` + assert(current == 'u'); + int codepoint = 0; + + const auto factors = { 12, 8, 4, 0 }; + for (const auto factor : factors) + { + get(); + + if (current >= '0' and current <= '9') + { + codepoint += ((current - 0x30) << factor); + } + else if (current >= 'A' and current <= 'F') + { + codepoint += ((current - 0x37) << factor); + } + else if (current >= 'a' and current <= 'f') + { + codepoint += ((current - 0x57) << factor); + } + else + { + return -1; + } + } + + assert(0x0000 <= codepoint and codepoint <= 0xFFFF); + return codepoint; + } + + /*! + @brief check if the next byte(s) are inside a given range + + Adds the current byte and, for each passed range, reads a new byte and + checks if it is inside the range. If a violation was detected, set up an + error message and return false. Otherwise, return true. + + @param[in] ranges list of integers; interpreted as list of pairs of + inclusive lower and upper bound, respectively + + @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, + 1, 2, or 3 pairs. This precondition is enforced by an assertion. + + @return true if and only if no range violation was detected + */ + bool next_byte_in_range(std::initializer_list ranges) + { + assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6); + add(current); + + for (auto range = ranges.begin(); range != ranges.end(); ++range) + { + get(); + if (JSON_LIKELY(*range <= current and current <= *(++range))) + { + add(current); + } + else + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; + } + } + + return true; + } + + /*! + @brief scan a string literal + + This function scans a string according to Sect. 7 of RFC 7159. While + scanning, bytes are escaped and copied into buffer token_buffer. Then the + function returns successfully, token_buffer is *not* null-terminated (as it + may contain \0 bytes), and token_buffer.size() is the number of bytes in the + string. + + @return token_type::value_string if string could be successfully scanned, + token_type::parse_error otherwise + + @note In case of errors, variable error_message contains a textual + description. + */ + token_type scan_string() + { + // reset token_buffer (ignore opening quote) + reset(); + + // we entered the function by reading an open quote + assert(current == '\"'); + + while (true) + { + // get next character + switch (get()) + { + // end of file while parsing string + case std::char_traits::eof(): + { + error_message = "invalid string: missing closing quote"; + return token_type::parse_error; + } + + // closing quote + case '\"': + { + return token_type::value_string; + } + + // escapes + case '\\': + { + switch (get()) + { + // quotation mark + case '\"': + add('\"'); + break; + // reverse solidus + case '\\': + add('\\'); + break; + // solidus + case '/': + add('/'); + break; + // backspace + case 'b': + add('\b'); + break; + // form feed + case 'f': + add('\f'); + break; + // line feed + case 'n': + add('\n'); + break; + // carriage return + case 'r': + add('\r'); + break; + // tab + case 't': + add('\t'); + break; + + // unicode escapes + case 'u': + { + const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; // start with codepoint1 + + if (JSON_UNLIKELY(codepoint1 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if code point is a high surrogate + if (0xD800 <= codepoint1 and codepoint1 <= 0xDBFF) + { + // expect next \uxxxx entry + if (JSON_LIKELY(get() == '\\' and get() == 'u')) + { + const int codepoint2 = get_codepoint(); + + if (JSON_UNLIKELY(codepoint2 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if codepoint2 is a low surrogate + if (JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF)) + { + // overwrite codepoint + codepoint = + // high surrogate occupies the most significant 22 bits + (codepoint1 << 10) + // low surrogate occupies the least significant 15 bits + + codepoint2 + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00; + } + else + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + if (JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF)) + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; + return token_type::parse_error; + } + } + + // result of the above calculation yields a proper codepoint + assert(0x00 <= codepoint and codepoint <= 0x10FFFF); + + // translate codepoint into bytes + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + add(codepoint); + } + else if (codepoint <= 0x7FF) + { + // 2-byte characters: 110xxxxx 10xxxxxx + add(0xC0 | (codepoint >> 6)); + add(0x80 | (codepoint & 0x3F)); + } + else if (codepoint <= 0xFFFF) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + add(0xE0 | (codepoint >> 12)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } + else + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + add(0xF0 | (codepoint >> 18)); + add(0x80 | ((codepoint >> 12) & 0x3F)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } + + break; + } + + // other characters after escape + default: + error_message = "invalid string: forbidden character after backslash"; + return token_type::parse_error; + } + + break; + } + + // invalid control characters + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + { + error_message = "invalid string: control character must be escaped"; + return token_type::parse_error; + } + + // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) + case 0x20: + case 0x21: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + { + add(current); + break; + } + + // U+0080..U+07FF: bytes C2..DF 80..BF + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: + { + if (JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF}))) + { + return token_type::parse_error; + } + break; + } + + // U+0800..U+0FFF: bytes E0 A0..BF 80..BF + case 0xE0: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF + // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xEE: + case 0xEF: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+D000..U+D7FF: bytes ED 80..9F 80..BF + case 0xED: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF + case 0xF0: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF + case 0xF1: + case 0xF2: + case 0xF3: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF + case 0xF4: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // remaining bytes (80..C1 and F5..FF) are ill-formed + default: + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return token_type::parse_error; + } + } + } + } + + static void strtof(float& f, const char* str, char** endptr) noexcept + { + f = std::strtof(str, endptr); + } + + static void strtof(double& f, const char* str, char** endptr) noexcept + { + f = std::strtod(str, endptr); + } + + static void strtof(long double& f, const char* str, char** endptr) noexcept + { + f = std::strtold(str, endptr); + } + + /*! + @brief scan a number literal + + This function scans a string according to Sect. 6 of RFC 7159. + + The function is realized with a deterministic finite state machine derived + from the grammar described in RFC 7159. Starting in state "init", the + input is read and used to determined the next state. Only state "done" + accepts the number. State "error" is a trap state to model errors. In the + table below, "anything" means any character but the ones listed before. + + state | 0 | 1-9 | e E | + | - | . | anything + ---------|----------|----------|----------|---------|---------|----------|----------- + init | zero | any1 | [error] | [error] | minus | [error] | [error] + minus | zero | any1 | [error] | [error] | [error] | [error] | [error] + zero | done | done | exponent | done | done | decimal1 | done + any1 | any1 | any1 | exponent | done | done | decimal1 | done + decimal1 | decimal2 | [error] | [error] | [error] | [error] | [error] | [error] + decimal2 | decimal2 | decimal2 | exponent | done | done | done | done + exponent | any2 | any2 | [error] | sign | sign | [error] | [error] + sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] + any2 | any2 | any2 | done | done | done | done | done + + The state machine is realized with one label per state (prefixed with + "scan_number_") and `goto` statements between them. The state machine + contains cycles, but any cycle can be left when EOF is read. Therefore, + the function is guaranteed to terminate. + + During scanning, the read bytes are stored in token_buffer. This string is + then converted to a signed integer, an unsigned integer, or a + floating-point number. + + @return token_type::value_unsigned, token_type::value_integer, or + token_type::value_float if number could be successfully scanned, + token_type::parse_error otherwise + + @note The scanner is independent of the current locale. Internally, the + locale's decimal point is used instead of `.` to work with the + locale-dependent converters. + */ + token_type scan_number() + { + // reset token_buffer to store the number's bytes + reset(); + + // the type of the parsed number; initially set to unsigned; will be + // changed if minus sign, decimal point or exponent is read + token_type number_type = token_type::value_unsigned; + + // state (init): we just found out we need to scan a number + switch (current) + { + case '-': + { + add(current); + goto scan_number_minus; + } + + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + // all other characters are rejected outside scan_number() + assert(false); // LCOV_EXCL_LINE + } + } + +scan_number_minus: + // state: we just parsed a leading minus sign + number_type = token_type::value_integer; + switch (get()) + { + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + error_message = "invalid number; expected digit after '-'"; + return token_type::parse_error; + } + } + +scan_number_zero: + // state: we just parse a zero (maybe with a leading minus sign) + switch (get()) + { + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_any1: + // state: we just parsed a number 0-9 (maybe with a leading minus sign) + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_decimal1: + // state: we just parsed a decimal point + number_type = token_type::value_float; + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + default: + { + error_message = "invalid number; expected digit after '.'"; + return token_type::parse_error; + } + } + +scan_number_decimal2: + // we just parsed at least one number after a decimal point + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_exponent: + // we just parsed an exponent + number_type = token_type::value_float; + switch (get()) + { + case '+': + case '-': + { + add(current); + goto scan_number_sign; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = + "invalid number; expected '+', '-', or digit after exponent"; + return token_type::parse_error; + } + } + +scan_number_sign: + // we just parsed an exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = "invalid number; expected digit after exponent sign"; + return token_type::parse_error; + } + } + +scan_number_any2: + // we just parsed a number after the exponent or exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + goto scan_number_done; + } + +scan_number_done: + // unget the character after the number (we only read it to know that + // we are done scanning a number) + unget(); + + char* endptr = nullptr; + errno = 0; + + // try to parse integers first and fall back to floats + if (number_type == token_type::value_unsigned) + { + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_unsigned = static_cast(x); + if (value_unsigned == x) + { + return token_type::value_unsigned; + } + } + } + else if (number_type == token_type::value_integer) + { + const auto x = std::strtoll(token_buffer.data(), &endptr, 10); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_integer = static_cast(x); + if (value_integer == x) + { + return token_type::value_integer; + } + } + } + + // this code is reached if we parse a floating-point number or if an + // integer conversion above failed + strtof(value_float, token_buffer.data(), &endptr); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + + return token_type::value_float; + } + + /*! + @param[in] literal_text the literal text to expect + @param[in] length the length of the passed literal text + @param[in] return_type the token type to return on success + */ + token_type scan_literal(const char* literal_text, const std::size_t length, + token_type return_type) + { + assert(current == literal_text[0]); + for (std::size_t i = 1; i < length; ++i) + { + if (JSON_UNLIKELY(get() != literal_text[i])) + { + error_message = "invalid literal"; + return token_type::parse_error; + } + } + return return_type; + } + + ///////////////////// + // input management + ///////////////////// + + /// reset token_buffer; current character is beginning of token + void reset() noexcept + { + token_buffer.clear(); + token_string.clear(); + token_string.push_back(std::char_traits::to_char_type(current)); + } + + /* + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a + `std::char_traits::eof()` in that case. Stores the scanned characters + for use in error messages. + + @return character read from the input + */ + std::char_traits::int_type get() + { + ++chars_read; + current = ia->get_character(); + if (JSON_LIKELY(current != std::char_traits::eof())) + { + token_string.push_back(std::char_traits::to_char_type(current)); + } + return current; + } + + /// unget current character (return it again on next get) + void unget() + { + --chars_read; + if (JSON_LIKELY(current != std::char_traits::eof())) + { + ia->unget_character(); + assert(token_string.size() != 0); + token_string.pop_back(); + } + } + + /// add a character to token_buffer + void add(int c) + { + token_buffer.push_back(std::char_traits::to_char_type(c)); + } + + public: + ///////////////////// + // value getters + ///////////////////// + + /// return integer value + constexpr number_integer_t get_number_integer() const noexcept + { + return value_integer; + } + + /// return unsigned integer value + constexpr number_unsigned_t get_number_unsigned() const noexcept + { + return value_unsigned; + } + + /// return floating-point value + constexpr number_float_t get_number_float() const noexcept + { + return value_float; + } + + /// return current string value (implicitly resets the token; useful only once) + std::string move_string() + { + return std::move(token_buffer); + } + + ///////////////////// + // diagnostics + ///////////////////// + + /// return position of last read token + constexpr std::size_t get_position() const noexcept + { + return chars_read; + } + + /// return the last read token (for errors only). Will never contain EOF + /// (an arbitrary value that is not a valid char value, often -1), because + /// 255 may legitimately occur. May contain NUL, which should be escaped. + std::string get_token_string() const + { + // escape control characters + std::string result; + for (const auto c : token_string) + { + if ('\x00' <= c and c <= '\x1F') + { + // escape control characters + std::stringstream ss; + ss << "(c) << ">"; + result += ss.str(); + } + else + { + // add character as is + result.push_back(c); + } + } + + return result; + } + + /// return syntax error message + constexpr const char* get_error_message() const noexcept + { + return error_message; + } + + ///////////////////// + // actual scanner + ///////////////////// + + token_type scan() + { + // read next character and ignore whitespace + do + { + get(); + } + while (current == ' ' or current == '\t' or current == '\n' or current == '\r'); + + switch (current) + { + // structural characters + case '[': + return token_type::begin_array; + case ']': + return token_type::end_array; + case '{': + return token_type::begin_object; + case '}': + return token_type::end_object; + case ':': + return token_type::name_separator; + case ',': + return token_type::value_separator; + + // literals + case 't': + return scan_literal("true", 4, token_type::literal_true); + case 'f': + return scan_literal("false", 5, token_type::literal_false); + case 'n': + return scan_literal("null", 4, token_type::literal_null); + + // string + case '\"': + return scan_string(); + + // number + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scan_number(); + + // end of input (the null byte is needed when parsing from + // string literals) + case '\0': + case std::char_traits::eof(): + return token_type::end_of_input; + + // error + default: + error_message = "invalid literal"; + return token_type::parse_error; + } + } + + private: + /// input adapter + detail::input_adapter_t ia = nullptr; + + /// the current character + std::char_traits::int_type current = std::char_traits::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// raw input token string (for error messages) + std::vector token_string {}; + + /// buffer for variable-length tokens (numbers, strings) + std::string token_buffer {}; + + /// a description of occurred lexer errors + const char* error_message = ""; + + // number values + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; + + /// the decimal point + const char decimal_point_char = '.'; +}; +} +} + +// #include + + +#include // assert +#include // isfinite +#include // uint8_t +#include // function +#include // string +#include // move + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +//////////// +// parser // +//////////// + +/*! +@brief syntax analysis + +This class implements a recursive decent parser. +*/ +template +class parser +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using lexer_t = lexer; + using token_type = typename lexer_t::token_type; + + public: + enum class parse_event_t : uint8_t + { + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value + }; + + using parser_callback_t = + std::function; + + /// a parser reading from an input adapter + explicit parser(detail::input_adapter_t adapter, + const parser_callback_t cb = nullptr, + const bool allow_exceptions_ = true) + : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_) + {} + + /*! + @brief public parser interface + + @param[in] strict whether to expect the last token to be EOF + @param[in,out] result parsed JSON value + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse(const bool strict, BasicJsonType& result) + { + // read first token + get_token(); + + parse_internal(true, result); + result.assert_invariant(); + + // in strict mode, input must be completely read + if (strict) + { + get_token(); + expect(token_type::end_of_input); + } + + // in case of an error, return discarded value + if (errored) + { + result = value_t::discarded; + return; + } + + // set top-level value to null if it was discarded by the callback + // function + if (result.is_discarded()) + { + result = nullptr; + } + } + + /*! + @brief public accept interface + + @param[in] strict whether to expect the last token to be EOF + @return whether the input is a proper JSON text + */ + bool accept(const bool strict = true) + { + // read first token + get_token(); + + if (not accept_internal()) + { + return false; + } + + // strict => last token must be EOF + return not strict or (get_token() == token_type::end_of_input); + } + + private: + /*! + @brief the actual parser + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse_internal(bool keep, BasicJsonType& result) + { + // never parse after a parse error was detected + assert(not errored); + + // start with a discarded value + if (not result.is_discarded()) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + + switch (last_token) + { + case token_type::begin_object: + { + if (keep) + { + if (callback) + { + keep = callback(depth++, parse_event_t::object_start, result); + } + + if (not callback or keep) + { + // explicitly set result to object to cope with {} + result.m_type = value_t::object; + result.m_value = value_t::object; + } + } + + // read next token + get_token(); + + // closing } -> we are done + if (last_token == token_type::end_object) + { + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + // parse values + std::string key; + BasicJsonType value; + while (true) + { + // store key + if (not expect(token_type::value_string)) + { + return; + } + key = m_lexer.move_string(); + + bool keep_tag = false; + if (keep) + { + if (callback) + { + BasicJsonType k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } + else + { + keep_tag = true; + } + } + + // parse separator (:) + get_token(); + if (not expect(token_type::name_separator)) + { + return; + } + + // parse and add value + get_token(); + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if (JSON_UNLIKELY(errored)) + { + return; + } + + if (keep and keep_tag and not value.is_discarded()) + { + result.m_value.object->emplace(std::move(key), std::move(value)); + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing } + if (not expect(token_type::end_object)) + { + return; + } + break; + } + + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + case token_type::begin_array: + { + if (keep) + { + if (callback) + { + keep = callback(depth++, parse_event_t::array_start, result); + } + + if (not callback or keep) + { + // explicitly set result to array to cope with [] + result.m_type = value_t::array; + result.m_value = value_t::array; + } + } + + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == token_type::end_array) + { + if (callback and not callback(--depth, parse_event_t::array_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + // parse values + BasicJsonType value; + while (true) + { + // parse value + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if (JSON_UNLIKELY(errored)) + { + return; + } + + if (keep and not value.is_discarded()) + { + result.m_value.array->push_back(std::move(value)); + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing ] + if (not expect(token_type::end_array)) + { + return; + } + break; + } + + if (keep and callback and not callback(--depth, parse_event_t::array_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + case token_type::literal_null: + { + result.m_type = value_t::null; + break; + } + + case token_type::value_string: + { + result.m_type = value_t::string; + result.m_value = m_lexer.move_string(); + break; + } + + case token_type::literal_true: + { + result.m_type = value_t::boolean; + result.m_value = true; + break; + } + + case token_type::literal_false: + { + result.m_type = value_t::boolean; + result.m_value = false; + break; + } + + case token_type::value_unsigned: + { + result.m_type = value_t::number_unsigned; + result.m_value = m_lexer.get_number_unsigned(); + break; + } + + case token_type::value_integer: + { + result.m_type = value_t::number_integer; + result.m_value = m_lexer.get_number_integer(); + break; + } + + case token_type::value_float: + { + result.m_type = value_t::number_float; + result.m_value = m_lexer.get_number_float(); + + // throw in case of infinity or NAN + if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float))) + { + if (allow_exceptions) + { + JSON_THROW(out_of_range::create(406, "number overflow parsing '" + + m_lexer.get_token_string() + "'")); + } + expect(token_type::uninitialized); + } + break; + } + + case token_type::parse_error: + { + // using "uninitialized" to avoid "expected" message + if (not expect(token_type::uninitialized)) + { + return; + } + break; // LCOV_EXCL_LINE + } + + default: + { + // the last token was unexpected; we expected a value + if (not expect(token_type::literal_or_value)) + { + return; + } + break; // LCOV_EXCL_LINE + } + } + + if (keep and callback and not callback(depth, parse_event_t::value, result)) + { + result.m_type = value_t::discarded; + } + } + + /*! + @brief the actual acceptor + + @invariant 1. The last token is not yet processed. Therefore, the caller + of this function must make sure a token has been read. + 2. When this function returns, the last token is processed. + That is, the last read character was already considered. + + This invariant makes sure that no token needs to be "unput". + */ + bool accept_internal() + { + switch (last_token) + { + case token_type::begin_object: + { + // read next token + get_token(); + + // closing } -> we are done + if (last_token == token_type::end_object) + { + return true; + } + + // parse values + while (true) + { + // parse key + if (last_token != token_type::value_string) + { + return false; + } + + // parse separator (:) + get_token(); + if (last_token != token_type::name_separator) + { + return false; + } + + // parse value + get_token(); + if (not accept_internal()) + { + return false; + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing } + return (last_token == token_type::end_object); + } + } + + case token_type::begin_array: + { + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == token_type::end_array) + { + return true; + } + + // parse values + while (true) + { + // parse value + if (not accept_internal()) + { + return false; + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing ] + return (last_token == token_type::end_array); + } + } + + case token_type::value_float: + { + // reject infinity or NAN + return std::isfinite(m_lexer.get_number_float()); + } + + case token_type::literal_false: + case token_type::literal_null: + case token_type::literal_true: + case token_type::value_integer: + case token_type::value_string: + case token_type::value_unsigned: + return true; + + default: // the last token was unexpected + return false; + } + } + + /// get next token from lexer + token_type get_token() + { + return (last_token = m_lexer.scan()); + } + + /*! + @throw parse_error.101 if expected token did not occur + */ + bool expect(token_type t) + { + if (JSON_UNLIKELY(t != last_token)) + { + errored = true; + expected = t; + if (allow_exceptions) + { + throw_exception(); + } + else + { + return false; + } + } + + return true; + } + + [[noreturn]] void throw_exception() const + { + std::string error_msg = "syntax error - "; + if (last_token == token_type::parse_error) + { + error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + + m_lexer.get_token_string() + "'"; + } + else + { + error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); + } + + if (expected != token_type::uninitialized) + { + error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); + } + + JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg)); + } + + private: + /// current level of recursion + int depth = 0; + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + token_type last_token = token_type::uninitialized; + /// the lexer + lexer_t m_lexer; + /// whether a syntax error occurred + bool errored = false; + /// possible reason for the syntax error + token_type expected = token_type::uninitialized; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; +} +} + +// #include + + +#include // ptrdiff_t +#include // numeric_limits + +namespace nlohmann +{ +namespace detail +{ +/* +@brief an iterator for primitive JSON types + +This class models an iterator for primitive JSON types (boolean, number, +string). It's only purpose is to allow the iterator/const_iterator classes +to "iterate" over primitive values. Internally, the iterator is modeled by +a `difference_type` variable. Value begin_value (`0`) models the begin, +end_value (`1`) models past the end. +*/ +class primitive_iterator_t +{ + private: + using difference_type = std::ptrdiff_t; + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + /// iterator as signed integer type + difference_type m_it = (std::numeric_limits::min)(); + + public: + constexpr difference_type get_value() const noexcept + { + return m_it; + } + + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return m_it == begin_value; + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return m_it == end_value; + } + + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it == rhs.m_it; + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + primitive_iterator_t operator+(difference_type n) noexcept + { + auto result = *this; + result += n; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + primitive_iterator_t& operator++() noexcept + { + ++m_it; + return *this; + } + + primitive_iterator_t const operator++(int) noexcept + { + auto result = *this; + m_it++; + return result; + } + + primitive_iterator_t& operator--() noexcept + { + --m_it; + return *this; + } + + primitive_iterator_t const operator--(int) noexcept + { + auto result = *this; + m_it--; + return result; + } + + primitive_iterator_t& operator+=(difference_type n) noexcept + { + m_it += n; + return *this; + } + + primitive_iterator_t& operator-=(difference_type n) noexcept + { + m_it -= n; + return *this; + } +}; +} +} + +// #include + + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/*! +@brief an iterator value + +@note This structure could easily be a union, but MSVC currently does not allow +unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. +*/ +template struct internal_iterator +{ + /// iterator for JSON objects + typename BasicJsonType::object_t::iterator object_iterator {}; + /// iterator for JSON arrays + typename BasicJsonType::array_t::iterator array_iterator {}; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator {}; +}; +} +} + +// #include + + +#include // not +#include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next +#include // conditional, is_const, remove_const + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// forward declare, to be able to friend it later on +template class iteration_proxy; + +/*! +@brief a template for a bidirectional iterator for the @ref basic_json class + +This class implements a both iterators (iterator and const_iterator) for the +@ref basic_json class. + +@note An iterator is called *initialized* when a pointer to a JSON value has + been set (e.g., by a constructor or a copy assignment). If the iterator is + default-constructed, it is *uninitialized* and most methods are undefined. + **The library uses assertions to detect calls on uninitialized iterators.** + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). + +@since version 1.0.0, simplified in version 2.0.9, change to bidirectional + iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) +*/ +template +class iter_impl +{ + /// allow basic_json to access private members + friend iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; + friend BasicJsonType; + friend iteration_proxy; + + using object_t = typename BasicJsonType::object_t; + using array_t = typename BasicJsonType::array_t; + // make sure BasicJsonType is basic_json or const basic_json + static_assert(is_basic_json::type>::value, + "iter_impl only accepts (const) basic_json"); + + public: + + /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. + /// The C++ Standard has never required user-defined iterators to derive from std::iterator. + /// A user-defined iterator should provide publicly accessible typedefs named + /// iterator_category, value_type, difference_type, pointer, and reference. + /// Note that value_type is required to be non-const, even for constant iterators. + using iterator_category = std::bidirectional_iterator_tag; + + /// the type of the values when the iterator is dereferenced + using value_type = typename BasicJsonType::value_type; + /// a type to represent differences between iterators + using difference_type = typename BasicJsonType::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename std::conditional::value, + typename BasicJsonType::const_pointer, + typename BasicJsonType::pointer>::type; + /// defines a reference to the type iterated over (value_type) + using reference = + typename std::conditional::value, + typename BasicJsonType::const_reference, + typename BasicJsonType::reference>::type; + + /// default constructor + iter_impl() = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit iter_impl(pointer object) noexcept : m_object(object) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /*! + @note The conventional copy constructor and copy assignment are implicitly + defined. Combined with the following converting constructor and + assignment, they support: (1) copy from iterator to iterator, (2) + copy from const iterator to const iterator, and (3) conversion from + iterator to const iterator. However conversion from const iterator + to iterator is not defined. + */ + + /*! + @brief converting constructor + @param[in] other non-const iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl(const iter_impl::type>& other) noexcept + : m_object(other.m_object), m_it(other.m_it) {} + + /*! + @brief converting assignment + @param[in,out] other non-const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl::type>& other) noexcept + { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + + private: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) + { + return m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator++(int) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, 1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator--(int) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, -1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator==(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + return (m_it.object_iterator == other.m_it.object_iterator); + + case value_t::array: + return (m_it.array_iterator == other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator!=(const iter_impl& other) const + { + return not operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); + + case value_t::array: + return (m_it.array_iterator < other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const iter_impl& other) const + { + return not other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const iter_impl& other) const + { + return not operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const iter_impl& other) const + { + return not operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief addition of distance and iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + friend iter_impl operator+(difference_type i, const iter_impl& it) + { + auto result = it; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const iter_impl& other) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + return m_it.array_iterator - other.m_it.array_iterator; + + default: + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); + + case value_t::array: + return *std::next(m_it.array_iterator, n); + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.get_value() == -n)) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + typename object_t::key_type key() const + { + assert(m_object != nullptr); + + if (JSON_LIKELY(m_object->is_object())) + { + return m_it.object_iterator->first; + } + + JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + private: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator::type> m_it; +}; +} +} + +// #include + + +#include // size_t +#include // string, to_string + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/// proxy class for the items() function +template class iteration_proxy +{ + private: + /// helper class for iteration + class iteration_proxy_internal + { + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + std::size_t array_index = 0; + + public: + explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {} + + /// dereference operator (needed for range-based for) + iteration_proxy_internal& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_internal& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// inequality operator (needed for range-based for) + bool operator!=(const iteration_proxy_internal& o) const noexcept + { + return anchor != o.anchor; + } + + /// return key of the iterator + std::string key() const + { + assert(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + return std::to_string(array_index); + + // use key from the object + case value_t::object: + return anchor.key(); + + // use an empty key for all primitive types + default: + return ""; + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } + }; + + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) noexcept + : container(cont) {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_internal begin() noexcept + { + return iteration_proxy_internal(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_internal end() noexcept + { + return iteration_proxy_internal(container.end()); + } +}; +} +} + +// #include + + +#include // ptrdiff_t +#include // reverse_iterator +#include // declval + +namespace nlohmann +{ +namespace detail +{ +////////////////////// +// reverse_iterator // +////////////////////// + +/*! +@brief a template for a reverse iterator class + +@tparam Base the base iterator type to reverse. Valid types are @ref +iterator (to create @ref reverse_iterator) and @ref const_iterator (to +create @ref const_reverse_iterator). + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +- [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + +@since version 1.0.0 +*/ +template +class json_reverse_iterator : public std::reverse_iterator +{ + public: + using difference_type = std::ptrdiff_t; + /// shortcut to the reverse iterator adapter + using base_iterator = std::reverse_iterator; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) {} + + /// create reverse iterator from base class + json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} + + /// post-increment (it++) + json_reverse_iterator const operator++(int) + { + return static_cast(base_iterator::operator++(1)); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + return static_cast(base_iterator::operator++()); + } + + /// post-decrement (it--) + json_reverse_iterator const operator--(int) + { + return static_cast(base_iterator::operator--(1)); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + return static_cast(base_iterator::operator--()); + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + return static_cast(base_iterator::operator+=(i)); + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + return static_cast(base_iterator::operator+(i)); + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + return static_cast(base_iterator::operator-(i)); + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return base_iterator(*this) - base_iterator(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + auto key() const -> decltype(std::declval().key()) + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } +}; +} +} + +// #include + + +#include // copy +#include // size_t +#include // streamsize +#include // back_inserter +#include // shared_ptr, make_shared +#include // basic_ostream +#include // basic_string +#include // vector + +namespace nlohmann +{ +namespace detail +{ +/// abstract output adapter interface +template struct output_adapter_protocol +{ + virtual void write_character(CharType c) = 0; + virtual void write_characters(const CharType* s, std::size_t length) = 0; + virtual ~output_adapter_protocol() = default; +}; + +/// a type to simplify interfaces +template +using output_adapter_t = std::shared_ptr>; + +/// output adapter for byte vectors +template +class output_vector_adapter : public output_adapter_protocol +{ + public: + explicit output_vector_adapter(std::vector& vec) : v(vec) {} + + void write_character(CharType c) override + { + v.push_back(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + std::copy(s, s + length, std::back_inserter(v)); + } + + private: + std::vector& v; +}; + +/// output adapter for output streams +template +class output_stream_adapter : public output_adapter_protocol +{ + public: + explicit output_stream_adapter(std::basic_ostream& s) : stream(s) {} + + void write_character(CharType c) override + { + stream.put(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + stream.write(s, static_cast(length)); + } + + private: + std::basic_ostream& stream; +}; + +/// output adapter for basic_string +template +class output_string_adapter : public output_adapter_protocol +{ + public: + explicit output_string_adapter(std::basic_string& s) : str(s) {} + + void write_character(CharType c) override + { + str.push_back(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + str.append(s, length); + } + + private: + std::basic_string& str; +}; + +template +class output_adapter +{ + public: + output_adapter(std::vector& vec) + : oa(std::make_shared>(vec)) {} + + output_adapter(std::basic_ostream& s) + : oa(std::make_shared>(s)) {} + + output_adapter(std::basic_string& s) + : oa(std::make_shared>(s)) {} + + operator output_adapter_t() + { + return oa; + } + + private: + output_adapter_t oa = nullptr; +}; +} +} + +// #include + + +#include // generate_n +#include // array +#include // assert +#include // ldexp +#include // size_t +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // setw, setfill +#include // hex +#include // back_inserter +#include // numeric_limits +#include // stringstream +#include // char_traits, string +#include // make_pair, move + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary reader // +/////////////////// + +/*! +@brief deserialization of CBOR and MessagePack values +*/ +template +class binary_reader +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using string_t = typename BasicJsonType::string_t; + + public: + /*! + @brief create a binary reader + + @param[in] adapter input adapter to read from + */ + explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter)) + { + assert(ia); + } + + /*! + @brief create a JSON value from CBOR input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from CBOR input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_cbor(const bool strict) + { + const auto res = parse_cbor_internal(); + if (strict) + { + get(); + expect_eof(); + } + return res; + } + + /*! + @brief create a JSON value from MessagePack input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from MessagePack input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_msgpack(const bool strict) + { + const auto res = parse_msgpack_internal(); + if (strict) + { + get(); + expect_eof(); + } + return res; + } + + /*! + @brief create a JSON value from UBJSON input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from UBJSON input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_ubjson(const bool strict) + { + const auto res = parse_ubjson_internal(); + if (strict) + { + get_ignore_noop(); + expect_eof(); + } + return res; + } + + /*! + @brief determine system byte order + + @return true if and only if system's byte order is little endian + + @note from http://stackoverflow.com/a/1001328/266378 + */ + static constexpr bool little_endianess(int num = 1) noexcept + { + return (*reinterpret_cast(&num) == 1); + } + + private: + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + BasicJsonType parse_cbor_internal(const bool get_char = true) + { + switch (get_char ? get() : current) + { + // EOF + case std::char_traits::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + return static_cast(current); + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + return get_number(); + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + return get_number(); + + case 0x1A: // Unsigned integer (four-byte uint32_t follows) + return get_number(); + + case 0x1B: // Unsigned integer (eight-byte uint64_t follows) + return get_number(); + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + return static_cast(0x20 - 1 - current); + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + return static_cast(-1) - get_number(); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + return static_cast(-1) - get_number(); + } + + case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) + { + return static_cast(-1) - get_number(); + } + + case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) + { + return static_cast(-1) - + static_cast(get_number()); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7F: // UTF-8 string (indefinite length) + { + return get_cbor_string(); + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + { + return get_cbor_array(current & 0x1F); + } + + case 0x98: // array (one-byte uint8_t for n follows) + { + return get_cbor_array(get_number()); + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + return get_cbor_array(get_number()); + } + + case 0x9A: // array (four-byte uint32_t for n follow) + { + return get_cbor_array(get_number()); + } + + case 0x9B: // array (eight-byte uint64_t for n follow) + { + return get_cbor_array(get_number()); + } + + case 0x9F: // array (indefinite length) + { + BasicJsonType result = value_t::array; + while (get() != 0xFF) + { + result.push_back(parse_cbor_internal(false)); + } + return result; + } + + // map (0x00..0x17 pairs of data items follow) + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + { + return get_cbor_object(current & 0x1F); + } + + case 0xB8: // map (one-byte uint8_t for n follows) + { + return get_cbor_object(get_number()); + } + + case 0xB9: // map (two-byte uint16_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBA: // map (four-byte uint32_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBB: // map (eight-byte uint64_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBF: // map (indefinite length) + { + BasicJsonType result = value_t::object; + while (get() != 0xFF) + { + auto key = get_cbor_string(); + result[key] = parse_cbor_internal(); + } + return result; + } + + case 0xF4: // false + { + return false; + } + + case 0xF5: // true + { + return true; + } + + case 0xF6: // null + { + return value_t::null; + } + + case 0xF9: // Half-Precision Float (two-byte IEEE 754) + { + const int byte1 = get(); + unexpect_eof(); + const int byte2 = get(); + unexpect_eof(); + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const int half = (byte1 << 8) + byte2; + const int exp = (half >> 10) & 0x1F; + const int mant = half & 0x3FF; + double val; + if (exp == 0) + { + val = std::ldexp(mant, -24); + } + else if (exp != 31) + { + val = std::ldexp(mant + 1024, exp - 25); + } + else + { + val = (mant == 0) ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + } + return (half & 0x8000) != 0 ? -val : val; + } + + case 0xFA: // Single-Precision Float (four-byte IEEE 754) + { + return get_number(); + } + + case 0xFB: // Double-Precision Float (eight-byte IEEE 754) + { + return get_number(); + } + + default: // anything else (0xFF is handled inside the other types) + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, "error reading CBOR; last byte: 0x" + ss.str())); + } + } + } + + BasicJsonType parse_msgpack_internal() + { + switch (get()) + { + // EOF + case std::char_traits::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + // positive fixint + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + return static_cast(current); + + // fixmap + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + { + return get_msgpack_object(current & 0x0F); + } + + // fixarray + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + case 0x98: + case 0x99: + case 0x9A: + case 0x9B: + case 0x9C: + case 0x9D: + case 0x9E: + case 0x9F: + { + return get_msgpack_array(current & 0x0F); + } + + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + return get_msgpack_string(); + + case 0xC0: // nil + return value_t::null; + + case 0xC2: // false + return false; + + case 0xC3: // true + return true; + + case 0xCA: // float 32 + return get_number(); + + case 0xCB: // float 64 + return get_number(); + + case 0xCC: // uint 8 + return get_number(); + + case 0xCD: // uint 16 + return get_number(); + + case 0xCE: // uint 32 + return get_number(); + + case 0xCF: // uint 64 + return get_number(); + + case 0xD0: // int 8 + return get_number(); + + case 0xD1: // int 16 + return get_number(); + + case 0xD2: // int 32 + return get_number(); + + case 0xD3: // int 64 + return get_number(); + + case 0xD9: // str 8 + case 0xDA: // str 16 + case 0xDB: // str 32 + return get_msgpack_string(); + + case 0xDC: // array 16 + { + return get_msgpack_array(get_number()); + } + + case 0xDD: // array 32 + { + return get_msgpack_array(get_number()); + } + + case 0xDE: // map 16 + { + return get_msgpack_object(get_number()); + } + + case 0xDF: // map 32 + { + return get_msgpack_object(get_number()); + } + + // positive fixint + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: + case 0xEF: + case 0xF0: + case 0xF1: + case 0xF2: + case 0xF3: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: + case 0xF8: + case 0xF9: + case 0xFA: + case 0xFB: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + return static_cast(current); + + default: // anything else + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading MessagePack; last byte: 0x" + ss.str())); + } + } + } + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + BasicJsonType parse_ubjson_internal(const bool get_char = true) + { + return get_ubjson_value(get_char ? get_ignore_noop() : current); + } + + /*! + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a -'ve valued + `std::char_traits::eof()` in that case. + + @return character read from the input + */ + int get() + { + ++chars_read; + return (current = ia->get_character()); + } + + /*! + @return character read from the input after ignoring all 'N' entries + */ + int get_ignore_noop() + { + do + { + get(); + } + while (current == 'N'); + + return current; + } + + /* + @brief read a number from the input + + @tparam NumberType the type of the number + + @return number of type @a NumberType + + @note This function needs to respect the system's endianess, because + bytes in CBOR and MessagePack are stored in network order (big + endian) and therefore need reordering on little endian systems. + + @throw parse_error.110 if input has less than `sizeof(NumberType)` bytes + */ + template NumberType get_number() + { + // step 1: read input into array with system's byte order + std::array vec; + for (std::size_t i = 0; i < sizeof(NumberType); ++i) + { + get(); + unexpect_eof(); + + // reverse byte order prior to conversion if necessary + if (is_little_endian) + { + vec[sizeof(NumberType) - i - 1] = static_cast(current); + } + else + { + vec[i] = static_cast(current); // LCOV_EXCL_LINE + } + } + + // step 2: convert array into number of type T and return + NumberType result; + std::memcpy(&result, vec.data(), sizeof(NumberType)); + return result; + } + + /*! + @brief create a string by reading characters from the input + + @param[in] len number of bytes to read + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of string memory. + + @return string created by reading @a len bytes + + @throw parse_error.110 if input has less than @a len bytes + */ + template + string_t get_string(const NumberType len) + { + string_t result; + std::generate_n(std::back_inserter(result), len, [this]() + { + get(); + unexpect_eof(); + return static_cast(current); + }); + return result; + } + + /*! + @brief reads a CBOR string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + Additionally, CBOR's strings with indefinite lengths are supported. + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + string_t get_cbor_string() + { + unexpect_eof(); + + switch (current) + { + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + return get_string(current & 0x1F); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + return get_string(get_number()); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + return get_string(get_number()); + } + + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + { + return get_string(get_number()); + } + + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + { + return get_string(get_number()); + } + + case 0x7F: // UTF-8 string (indefinite length) + { + string_t result; + while (get() != 0xFF) + { + result.append(get_cbor_string()); + } + return result; + } + + default: + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, "expected a CBOR string; last byte: 0x" + ss.str())); + } + } + } + + template + BasicJsonType get_cbor_array(const NumberType len) + { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_cbor_internal(); + }); + return result; + } + + template + BasicJsonType get_cbor_object(const NumberType len) + { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() + { + get(); + auto key = get_cbor_string(); + auto val = parse_cbor_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + + /*! + @brief reads a MessagePack string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + string_t get_msgpack_string() + { + unexpect_eof(); + + switch (current) + { + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + { + return get_string(current & 0x1F); + } + + case 0xD9: // str 8 + { + return get_string(get_number()); + } + + case 0xDA: // str 16 + { + return get_string(get_number()); + } + + case 0xDB: // str 32 + { + return get_string(get_number()); + } + + default: + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a MessagePack string; last byte: 0x" + ss.str())); + } + } + } + + template + BasicJsonType get_msgpack_array(const NumberType len) + { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_msgpack_internal(); + }); + return result; + } + + template + BasicJsonType get_msgpack_object(const NumberType len) + { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() + { + get(); + auto key = get_msgpack_string(); + auto val = parse_msgpack_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + + /*! + @brief reads a UBJSON string + + This function is either called after reading the 'S' byte explicitly + indicating a string, or in case of an object key where the 'S' byte can be + left out. + + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + string_t get_ubjson_string(const bool get_char = true) + { + if (get_char) + { + get(); // TODO: may we ignore N here? + } + + unexpect_eof(); + + switch (current) + { + case 'U': + return get_string(get_number()); + case 'i': + return get_string(get_number()); + case 'I': + return get_string(get_number()); + case 'l': + return get_string(get_number()); + case 'L': + return get_string(get_number()); + default: + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a UBJSON string; last byte: 0x" + ss.str())); + } + } + + /*! + @brief determine the type and size for a container + + In the optimized UBJSON format, a type and a size can be provided to allow + for a more compact representation. + + @return pair of the size and the type + */ + std::pair get_ubjson_size_type() + { + std::size_t sz = string_t::npos; + int tc = 0; + + get_ignore_noop(); + + if (current == '$') + { + tc = get(); // must not ignore 'N', because 'N' maybe the type + unexpect_eof(); + + get_ignore_noop(); + if (current != '#') + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "expected '#' after UBJSON type information; last byte: 0x" + ss.str())); + } + sz = parse_ubjson_internal(); + } + else if (current == '#') + { + sz = parse_ubjson_internal(); + } + + return std::make_pair(sz, tc); + } + + BasicJsonType get_ubjson_value(const int prefix) + { + switch (prefix) + { + case std::char_traits::eof(): // EOF + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + case 'T': // true + return true; + case 'F': // false + return false; + + case 'Z': // null + return nullptr; + + case 'U': + return get_number(); + case 'i': + return get_number(); + case 'I': + return get_number(); + case 'l': + return get_number(); + case 'L': + return get_number(); + case 'd': + return get_number(); + case 'D': + return get_number(); + + case 'C': // char + { + get(); + unexpect_eof(); + if (JSON_UNLIKELY(current > 127)) + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + ss.str())); + } + return string_t(1, static_cast(current)); + } + + case 'S': // string + return get_ubjson_string(); + + case '[': // array + return get_ubjson_array(); + + case '{': // object + return get_ubjson_object(); + + default: // anything else + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading UBJSON; last byte: 0x" + ss.str())); + } + } + + BasicJsonType get_ubjson_array() + { + BasicJsonType result = value_t::array; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != string_t::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive array size: " + std::to_string(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this, size_and_type]() + { + return get_ubjson_value(size_and_type.second); + }); + } + } + else + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this]() + { + return parse_ubjson_internal(); + }); + } + } + else + { + while (current != ']') + { + result.push_back(parse_ubjson_internal(false)); + get_ignore_noop(); + } + } + + return result; + } + + BasicJsonType get_ubjson_object() + { + BasicJsonType result = value_t::object; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != string_t::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive object size: " + std::to_string(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this, size_and_type]() + { + auto key = get_ubjson_string(); + auto val = get_ubjson_value(size_and_type.second); + return std::make_pair(std::move(key), std::move(val)); + }); + } + else + { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this]() + { + auto key = get_ubjson_string(); + auto val = parse_ubjson_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + } + } + else + { + while (current != '}') + { + auto key = get_ubjson_string(false); + result[std::move(key)] = parse_ubjson_internal(); + get_ignore_noop(); + } + } + + return result; + } + + /*! + @brief throw if end of input is not reached + @throw parse_error.110 if input not ended + */ + void expect_eof() const + { + if (JSON_UNLIKELY(current != std::char_traits::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "expected end of input")); + } + } + + /*! + @briefthrow if end of input is reached + @throw parse_error.110 if input ended + */ + void unexpect_eof() const + { + if (JSON_UNLIKELY(current == std::char_traits::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + } + } + + private: + /// input adapter + input_adapter_t ia = nullptr; + + /// the current character + int current = std::char_traits::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// whether we can assume little endianess + const bool is_little_endian = little_endianess(); +}; +} +} + +// #include + + +#include // reverse +#include // array +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // numeric_limits + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary writer // +/////////////////// + +/*! +@brief serialization to CBOR and MessagePack values +*/ +template +class binary_writer +{ + public: + /*! + @brief create a binary writer + + @param[in] adapter output adapter to write to + */ + explicit binary_writer(output_adapter_t adapter) : oa(adapter) + { + assert(oa); + } + + /*! + @brief[in] j JSON value to serialize + */ + void write_cbor(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: + { + oa->write_character(static_cast(0xF6)); + break; + } + + case value_t::boolean: + { + oa->write_character(j.m_value.boolean + ? static_cast(0xF5) + : static_cast(0xF4)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_value.number_integer <= 0x17) + { + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x18)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x19)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x1A)); + write_number(static_cast(j.m_value.number_integer)); + } + else + { + oa->write_character(static_cast(0x1B)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + // The conversions below encode the sign in the first + // byte, and the value is converted to a positive number. + const auto positive_number = -1 - j.m_value.number_integer; + if (j.m_value.number_integer >= -24) + { + write_number(static_cast(0x20 + positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x38)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x39)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x3A)); + write_number(static_cast(positive_number)); + } + else + { + oa->write_character(static_cast(0x3B)); + write_number(static_cast(positive_number)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= 0x17) + { + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x18)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x19)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x1A)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else + { + oa->write_character(static_cast(0x1B)); + write_number(static_cast(j.m_value.number_unsigned)); + } + break; + } + + case value_t::number_float: // Double-Precision Float + { + oa->write_character(static_cast(0xFB)); + write_number(j.m_value.number_float); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 0x17) + { + write_number(static_cast(0x60 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x78)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x79)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x7A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x7B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 0x17) + { + write_number(static_cast(0x80 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x98)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x99)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x9A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x9B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_cbor(el); + } + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 0x17) + { + write_number(static_cast(0xA0 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xB8)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xB9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xBA)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xBB)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_cbor(el.first); + write_cbor(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @brief[in] j JSON value to serialize + */ + void write_msgpack(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: // nil + { + oa->write_character(static_cast(0xC0)); + break; + } + + case value_t::boolean: // true and false + { + oa->write_character(j.m_value.boolean + ? static_cast(0xC3) + : static_cast(0xC2)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(static_cast(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(static_cast(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(static_cast(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(static_cast(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + if (j.m_value.number_integer >= -32) + { + // negative fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 8 + oa->write_character(static_cast(0xD0)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 16 + oa->write_character(static_cast(0xD1)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 32 + oa->write_character(static_cast(0xD2)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 64 + oa->write_character(static_cast(0xD3)); + write_number(static_cast(j.m_value.number_integer)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(static_cast(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(static_cast(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(static_cast(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(static_cast(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + break; + } + + case value_t::number_float: // float 64 + { + oa->write_character(static_cast(0xCB)); + write_number(j.m_value.number_float); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 31) + { + // fixstr + write_number(static_cast(0xA0 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 8 + oa->write_character(static_cast(0xD9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 16 + oa->write_character(static_cast(0xDA)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 32 + oa->write_character(static_cast(0xDB)); + write_number(static_cast(N)); + } + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 15) + { + // fixarray + write_number(static_cast(0x90 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 16 + oa->write_character(static_cast(0xDC)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 32 + oa->write_character(static_cast(0xDD)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_msgpack(el); + } + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 15) + { + // fixmap + write_number(static_cast(0x80 | (N & 0xF))); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 16 + oa->write_character(static_cast(0xDE)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 32 + oa->write_character(static_cast(0xDF)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_msgpack(el.first); + write_msgpack(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + @param[in] use_count whether to use '#' prefixes (optimized format) + @param[in] use_type whether to use '$' prefixes (optimized format) + @param[in] add_prefix whether prefixes need to be used for this value + */ + void write_ubjson(const BasicJsonType& j, const bool use_count, + const bool use_type, const bool add_prefix = true) + { + switch (j.type()) + { + case value_t::null: + { + if (add_prefix) + { + oa->write_character(static_cast('Z')); + } + break; + } + + case value_t::boolean: + { + if (add_prefix) + oa->write_character(j.m_value.boolean + ? static_cast('T') + : static_cast('F')); + break; + } + + case value_t::number_integer: + { + write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); + break; + } + + case value_t::number_unsigned: + { + write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); + break; + } + + case value_t::number_float: + { + write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); + break; + } + + case value_t::string: + { + if (add_prefix) + { + oa->write_character(static_cast('S')); + } + write_number_with_ubjson_prefix(j.m_value.string->size(), true); + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + if (add_prefix) + { + oa->write_character(static_cast('[')); + } + + bool prefix_required = true; + if (use_type and not j.m_value.array->empty()) + { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin() + 1, j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(static_cast('$')); + oa->write_character(static_cast(first_prefix)); + } + } + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.array->size(), true); + } + + for (const auto& el : *j.m_value.array) + { + write_ubjson(el, use_count, use_type, prefix_required); + } + + if (not use_count) + { + oa->write_character(static_cast(']')); + } + + break; + } + + case value_t::object: + { + if (add_prefix) + { + oa->write_character(static_cast('{')); + } + + bool prefix_required = true; + if (use_type and not j.m_value.object->empty()) + { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin(), j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(static_cast('$')); + oa->write_character(static_cast(first_prefix)); + } + } + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.object->size(), true); + } + + for (const auto& el : *j.m_value.object) + { + write_number_with_ubjson_prefix(el.first.size(), true); + oa->write_characters( + reinterpret_cast(el.first.c_str()), + el.first.size()); + write_ubjson(el.second, use_count, use_type, prefix_required); + } + + if (not use_count) + { + oa->write_character(static_cast('}')); + } + + break; + } + + default: + break; + } + } + + private: + /* + @brief write a number to output input + + @param[in] n number of type @a NumberType + @tparam NumberType the type of the number + + @note This function needs to respect the system's endianess, because bytes + in CBOR, MessagePack, and UBJSON are stored in network order (big + endian) and therefore need reordering on little endian systems. + */ + template + void write_number(const NumberType n) + { + // step 1: write number to array of length NumberType + std::array vec; + std::memcpy(vec.data(), &n, sizeof(NumberType)); + + // step 2: write array to output (with possible reordering) + if (is_little_endian) + { + // reverse byte order prior to conversion if necessary + std::reverse(vec.begin(), vec.end()); + } + + oa->write_characters(vec.data(), sizeof(NumberType)); + } + + template + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (std::is_floating_point::value) + { + if (add_prefix) + { + oa->write_character(static_cast('D')); // float64 + } + write_number(n); + } + else if (std::is_unsigned::value) + { + if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('i')); // int8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('U')); // uint8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('I')); // int16 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('l')); // int32 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('L')); // int64 + } + write_number(static_cast(n)); + } + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n))); + } + } + else + { + if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('i')); // int8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('U')); // uint8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('I')); // int16 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('l')); // int32 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('L')); // int64 + } + write_number(static_cast(n)); + } + // LCOV_EXCL_START + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n))); + } + // LCOV_EXCL_STOP + } + } + + /*! + @brief determine the type prefix of container values + + @note This function does not need to be 100% accurate when it comes to + integer limits. In case a number exceeds the limits of int64_t, + this will be detected by a later call to function + write_number_with_ubjson_prefix. Therefore, we return 'L' for any + value that does not fit the previous limits. + */ + char ubjson_prefix(const BasicJsonType& j) const noexcept + { + switch (j.type()) + { + case value_t::null: + return 'Z'; + + case value_t::boolean: + return j.m_value.boolean ? 'T' : 'F'; + + case value_t::number_integer: + { + if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_float: + return 'D'; + + case value_t::string: + return 'S'; + + case value_t::array: + return '['; + + case value_t::object: + return '{'; + + default: // discarded values + return 'N'; + } + } + + private: + /// whether we can assume little endianess + const bool is_little_endian = binary_reader::little_endianess(); + + /// the output + output_adapter_t oa = nullptr; +}; +} +} + +// #include + + +#include // reverse, remove, fill, find, none_of +#include // array +#include // assert +#include // and, or +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // setfill +#include // next +#include // numeric_limits +#include // string +#include // stringstream +#include // is_same + +// #include + +// #include + + +#include // assert +#include // or, and, not +#include // signbit, isfinite +#include // intN_t, uintN_t +#include // memcpy, memmove + +namespace nlohmann +{ +namespace detail +{ + +/*! +@brief implements the Grisu2 algorithm for binary to decimal floating-point +conversion. + +This implementation is a slightly modified version of the reference +implementation which may be obtained from +http://florian.loitsch.com/publications (bench.tar.gz). + +The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. + +For a detailed description of the algorithm see: + +[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with + Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming + Language Design and Implementation, PLDI 2010 +[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", + Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language + Design and Implementation, PLDI 1996 +*/ +namespace dtoa_impl +{ + +template +Target reinterpret_bits(const Source source) +{ + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); + + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; +} + +struct diyfp // f * 2^e +{ + static constexpr int kPrecision = 64; // = q + + uint64_t f; + int e; + + constexpr diyfp() noexcept : f(0), e(0) {} + constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + /*! + @brief returns x - y + @pre x.e == y.e and x.f >= y.f + */ + static diyfp sub(const diyfp& x, const diyfp& y) noexcept + { + assert(x.e == y.e); + assert(x.f >= y.f); + + return diyfp(x.f - y.f, x.e); + } + + /*! + @brief returns x * y + @note The result is rounded. (Only the upper q bits are returned.) + */ + static diyfp mul(const diyfp& x, const diyfp& y) noexcept + { + static_assert(kPrecision == 64, "internal error"); + + // Computes: + // f = round((x.f * y.f) / 2^q) + // e = x.e + y.e + q + + // Emulate the 64-bit * 64-bit multiplication: + // + // p = u * v + // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) + // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) + // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) + // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) + // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) + // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) + // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) + // + // (Since Q might be larger than 2^32 - 1) + // + // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) + // + // (Q_hi + H does not overflow a 64-bit int) + // + // = p_lo + 2^64 p_hi + + const uint64_t u_lo = x.f & 0xFFFFFFFF; + const uint64_t u_hi = x.f >> 32; + const uint64_t v_lo = y.f & 0xFFFFFFFF; + const uint64_t v_hi = y.f >> 32; + + const uint64_t p0 = u_lo * v_lo; + const uint64_t p1 = u_lo * v_hi; + const uint64_t p2 = u_hi * v_lo; + const uint64_t p3 = u_hi * v_hi; + + const uint64_t p0_hi = p0 >> 32; + const uint64_t p1_lo = p1 & 0xFFFFFFFF; + const uint64_t p1_hi = p1 >> 32; + const uint64_t p2_lo = p2 & 0xFFFFFFFF; + const uint64_t p2_hi = p2 >> 32; + + uint64_t Q = p0_hi + p1_lo + p2_lo; + + // The full product might now be computed as + // + // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) + // p_lo = p0_lo + (Q << 32) + // + // But in this particular case here, the full p_lo is not required. + // Effectively we only need to add the highest bit in p_lo to p_hi (and + // Q_hi + 1 does not overflow). + + Q += uint64_t{1} << (64 - 32 - 1); // round, ties up + + const uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32); + + return diyfp(h, x.e + y.e + 64); + } + + /*! + @brief normalize x such that the significand is >= 2^(q-1) + @pre x.f != 0 + */ + static diyfp normalize(diyfp x) noexcept + { + assert(x.f != 0); + + while ((x.f >> 63) == 0) + { + x.f <<= 1; + x.e--; + } + + return x; + } + + /*! + @brief normalize x such that the result has the exponent E + @pre e >= x.e and the upper e - x.e bits of x.f must be zero. + */ + static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept + { + const int delta = x.e - target_exponent; + + assert(delta >= 0); + assert(((x.f << delta) >> delta) == x.f); + + return diyfp(x.f << delta, target_exponent); + } +}; + +struct boundaries +{ + diyfp w; + diyfp minus; + diyfp plus; +}; + +/*! +Compute the (normalized) diyfp representing the input number 'value' and its +boundaries. + +@pre value must be finite and positive +*/ +template +boundaries compute_boundaries(FloatType value) +{ + assert(std::isfinite(value)); + assert(value > 0); + + // Convert the IEEE representation into a diyfp. + // + // If v is denormal: + // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) + // If v is normalized: + // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) + + static_assert(std::numeric_limits::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + + constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) + constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); + constexpr int kMinExp = 1 - kBias; + constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1) + + using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type; + + const uint64_t bits = reinterpret_bits(value); + const uint64_t E = bits >> (kPrecision - 1); + const uint64_t F = bits & (kHiddenBit - 1); + + const bool is_denormal = (E == 0); + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F + kHiddenBit, static_cast(E) - kBias); + + // Compute the boundaries m- and m+ of the floating-point value + // v = f * 2^e. + // + // Determine v- and v+, the floating-point predecessor and successor if v, + // respectively. + // + // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) + // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) + // + // v+ = v + 2^e + // + // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ + // between m- and m+ round to v, regardless of how the input rounding + // algorithm breaks ties. + // + // ---+-------------+-------------+-------------+-------------+--- (A) + // v- m- v m+ v+ + // + // -----------------+------+------+-------------+-------------+--- (B) + // v- m- v m+ v+ + + const bool lower_boundary_is_closer = (F == 0 and E > 1); + const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp(4 * v.f - 1, v.e - 2) // (B) + : diyfp(2 * v.f - 1, v.e - 1); // (A) + + // Determine the normalized w+ = m+. + const diyfp w_plus = diyfp::normalize(m_plus); + + // Determine w- = m- such that e_(w-) = e_(w+). + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// Given normalized diyfp w, Grisu needs to find a (normalized) cached +// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies +// within a certain range [alpha, gamma] (Definition 3.2 from [1]) +// +// alpha <= e = e_c + e_w + q <= gamma +// +// or +// +// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q +// <= f_c * f_w * 2^gamma +// +// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies +// +// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma +// +// or +// +// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) +// +// The choice of (alpha,gamma) determines the size of the table and the form of +// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well +// in practice: +// +// The idea is to cut the number c * w = f * 2^e into two parts, which can be +// processed independently: An integral part p1, and a fractional part p2: +// +// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e +// = (f div 2^-e) + (f mod 2^-e) * 2^e +// = p1 + p2 * 2^e +// +// The conversion of p1 into decimal form requires a series of divisions and +// modulos by (a power of) 10. These operations are faster for 32-bit than for +// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be +// achieved by choosing +// +// -e >= 32 or e <= -32 := gamma +// +// In order to convert the fractional part +// +// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... +// +// into decimal form, the fraction is repeatedly multiplied by 10 and the digits +// d[-i] are extracted in order: +// +// (10 * p2) div 2^-e = d[-1] +// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... +// +// The multiplication by 10 must not overflow. It is sufficient to choose +// +// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. +// +// Since p2 = f mod 2^-e < 2^-e, +// +// -e <= 60 or e >= -60 := alpha + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power // c = f * 2^e ~= 10^k +{ + uint64_t f; + int e; + int k; +}; + +/*! +For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached +power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c +satisfies (Definition 3.2 from [1]) + + alpha <= e_c + e + q <= gamma. +*/ +inline cached_power get_cached_power_for_binary_exponent(int e) +{ + // Now + // + // alpha <= e_c + e + q <= gamma (1) + // ==> f_c * 2^alpha <= c * 2^e * 2^q + // + // and since the c's are normalized, 2^(q-1) <= f_c, + // + // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) + // ==> 2^(alpha - e - 1) <= c + // + // If c were an exakt power of ten, i.e. c = 10^k, one may determine k as + // + // k = ceil( log_10( 2^(alpha - e - 1) ) ) + // = ceil( (alpha - e - 1) * log_10(2) ) + // + // From the paper: + // "In theory the result of the procedure could be wrong since c is rounded, + // and the computation itself is approximated [...]. In practice, however, + // this simple function is sufficient." + // + // For IEEE double precision floating-point numbers converted into + // normalized diyfp's w = f * 2^e, with q = 64, + // + // e >= -1022 (min IEEE exponent) + // -52 (p - 1) + // -52 (p - 1, possibly normalize denormal IEEE numbers) + // -11 (normalize the diyfp) + // = -1137 + // + // and + // + // e <= +1023 (max IEEE exponent) + // -52 (p - 1) + // -11 (normalize the diyfp) + // = 960 + // + // This binary exponent range [-1137,960] results in a decimal exponent + // range [-307,324]. One does not need to store a cached power for each + // k in this range. For each such k it suffices to find a cached power + // such that the exponent of the product lies in [alpha,gamma]. + // This implies that the difference of the decimal exponents of adjacent + // table entries must be less than or equal to + // + // floor( (gamma - alpha) * log_10(2) ) = 8. + // + // (A smaller distance gamma-alpha would require a larger table.) + + // NB: + // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. + + constexpr int kCachedPowersSize = 79; + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr cached_power kCachedPowers[] = + { + { 0xAB70FE17C79AC6CA, -1060, -300 }, + { 0xFF77B1FCBEBCDC4F, -1034, -292 }, + { 0xBE5691EF416BD60C, -1007, -284 }, + { 0x8DD01FAD907FFC3C, -980, -276 }, + { 0xD3515C2831559A83, -954, -268 }, + { 0x9D71AC8FADA6C9B5, -927, -260 }, + { 0xEA9C227723EE8BCB, -901, -252 }, + { 0xAECC49914078536D, -874, -244 }, + { 0x823C12795DB6CE57, -847, -236 }, + { 0xC21094364DFB5637, -821, -228 }, + { 0x9096EA6F3848984F, -794, -220 }, + { 0xD77485CB25823AC7, -768, -212 }, + { 0xA086CFCD97BF97F4, -741, -204 }, + { 0xEF340A98172AACE5, -715, -196 }, + { 0xB23867FB2A35B28E, -688, -188 }, + { 0x84C8D4DFD2C63F3B, -661, -180 }, + { 0xC5DD44271AD3CDBA, -635, -172 }, + { 0x936B9FCEBB25C996, -608, -164 }, + { 0xDBAC6C247D62A584, -582, -156 }, + { 0xA3AB66580D5FDAF6, -555, -148 }, + { 0xF3E2F893DEC3F126, -529, -140 }, + { 0xB5B5ADA8AAFF80B8, -502, -132 }, + { 0x87625F056C7C4A8B, -475, -124 }, + { 0xC9BCFF6034C13053, -449, -116 }, + { 0x964E858C91BA2655, -422, -108 }, + { 0xDFF9772470297EBD, -396, -100 }, + { 0xA6DFBD9FB8E5B88F, -369, -92 }, + { 0xF8A95FCF88747D94, -343, -84 }, + { 0xB94470938FA89BCF, -316, -76 }, + { 0x8A08F0F8BF0F156B, -289, -68 }, + { 0xCDB02555653131B6, -263, -60 }, + { 0x993FE2C6D07B7FAC, -236, -52 }, + { 0xE45C10C42A2B3B06, -210, -44 }, + { 0xAA242499697392D3, -183, -36 }, + { 0xFD87B5F28300CA0E, -157, -28 }, + { 0xBCE5086492111AEB, -130, -20 }, + { 0x8CBCCC096F5088CC, -103, -12 }, + { 0xD1B71758E219652C, -77, -4 }, + { 0x9C40000000000000, -50, 4 }, + { 0xE8D4A51000000000, -24, 12 }, + { 0xAD78EBC5AC620000, 3, 20 }, + { 0x813F3978F8940984, 30, 28 }, + { 0xC097CE7BC90715B3, 56, 36 }, + { 0x8F7E32CE7BEA5C70, 83, 44 }, + { 0xD5D238A4ABE98068, 109, 52 }, + { 0x9F4F2726179A2245, 136, 60 }, + { 0xED63A231D4C4FB27, 162, 68 }, + { 0xB0DE65388CC8ADA8, 189, 76 }, + { 0x83C7088E1AAB65DB, 216, 84 }, + { 0xC45D1DF942711D9A, 242, 92 }, + { 0x924D692CA61BE758, 269, 100 }, + { 0xDA01EE641A708DEA, 295, 108 }, + { 0xA26DA3999AEF774A, 322, 116 }, + { 0xF209787BB47D6B85, 348, 124 }, + { 0xB454E4A179DD1877, 375, 132 }, + { 0x865B86925B9BC5C2, 402, 140 }, + { 0xC83553C5C8965D3D, 428, 148 }, + { 0x952AB45CFA97A0B3, 455, 156 }, + { 0xDE469FBD99A05FE3, 481, 164 }, + { 0xA59BC234DB398C25, 508, 172 }, + { 0xF6C69A72A3989F5C, 534, 180 }, + { 0xB7DCBF5354E9BECE, 561, 188 }, + { 0x88FCF317F22241E2, 588, 196 }, + { 0xCC20CE9BD35C78A5, 614, 204 }, + { 0x98165AF37B2153DF, 641, 212 }, + { 0xE2A0B5DC971F303A, 667, 220 }, + { 0xA8D9D1535CE3B396, 694, 228 }, + { 0xFB9B7CD9A4A7443C, 720, 236 }, + { 0xBB764C4CA7A44410, 747, 244 }, + { 0x8BAB8EEFB6409C1A, 774, 252 }, + { 0xD01FEF10A657842C, 800, 260 }, + { 0x9B10A4E5E9913129, 827, 268 }, + { 0xE7109BFBA19C0C9D, 853, 276 }, + { 0xAC2820D9623BF429, 880, 284 }, + { 0x80444B5E7AA7CF85, 907, 292 }, + { 0xBF21E44003ACDD2D, 933, 300 }, + { 0x8E679C2F5E44FF8F, 960, 308 }, + { 0xD433179D9C8CB841, 986, 316 }, + { 0x9E19DB92B4E31BA9, 1013, 324 }, + }; + + // This computation gives exactly the same results for k as + // k = ceil((kAlpha - e - 1) * 0.30102999566398114) + // for |e| <= 1500, but doesn't require floating-point operations. + // NB: log_10(2) ~= 78913 / 2^18 + assert(e >= -1500); + assert(e <= 1500); + const int f = kAlpha - e - 1; + const int k = (f * 78913) / (1 << 18) + (f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; + assert(index >= 0); + assert(index < kCachedPowersSize); + static_cast(kCachedPowersSize); // Fix warning. + + const cached_power cached = kCachedPowers[index]; + assert(kAlpha <= cached.e + e + 64); + assert(kGamma >= cached.e + e + 64); + + return cached; +} + +/*! +For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. +For n == 0, returns 1 and sets pow10 := 1. +*/ +inline int find_largest_pow10(const uint32_t n, uint32_t& pow10) +{ + // LCOV_EXCL_START + if (n >= 1000000000) + { + pow10 = 1000000000; + return 10; + } + // LCOV_EXCL_STOP + else if (n >= 100000000) + { + pow10 = 100000000; + return 9; + } + else if (n >= 10000000) + { + pow10 = 10000000; + return 8; + } + else if (n >= 1000000) + { + pow10 = 1000000; + return 7; + } + else if (n >= 100000) + { + pow10 = 100000; + return 6; + } + else if (n >= 10000) + { + pow10 = 10000; + return 5; + } + else if (n >= 1000) + { + pow10 = 1000; + return 4; + } + else if (n >= 100) + { + pow10 = 100; + return 3; + } + else if (n >= 10) + { + pow10 = 10; + return 2; + } + else + { + pow10 = 1; + return 1; + } +} + +inline void grisu2_round(char* buf, int len, uint64_t dist, uint64_t delta, + uint64_t rest, uint64_t ten_k) +{ + assert(len >= 1); + assert(dist <= delta); + assert(rest <= delta); + assert(ten_k > 0); + + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // ten_k + // <------> + // <---- rest ----> + // --------------[------------------+----+--------------]-------------- + // w V + // = buf * 10^k + // + // ten_k represents a unit-in-the-last-place in the decimal representation + // stored in buf. + // Decrement buf by ten_k while this takes buf closer to w. + + // The tests are written in this order to avoid overflow in unsigned + // integer arithmetic. + + while (rest < dist + and delta - rest >= ten_k + and (rest + ten_k < dist or dist - rest > rest + ten_k - dist)) + { + assert(buf[len - 1] != '0'); + buf[len - 1]--; + rest += ten_k; + } +} + +/*! +Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. +M- and M+ must be normalized and share the same exponent -60 <= e <= -32. +*/ +inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) +{ + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); + + // Generates the digits (and the exponent) of a decimal floating-point + // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's + // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. + // + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // Grisu2 generates the digits of M+ from left to right and stops as soon as + // V is in [M-,M+]. + + assert(M_plus.e >= kAlpha); + assert(M_plus.e <= kGamma); + + uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) + uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) + + // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): + // + // M+ = f * 2^e + // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e + // = ((p1 ) * 2^-e + (p2 )) * 2^e + // = p1 + p2 * 2^e + + const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e); + + uint32_t p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) + uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e + + // 1) + // + // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] + + assert(p1 > 0); + + uint32_t pow10; + const int k = find_largest_pow10(p1, pow10); + + // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) + // + // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) + // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) + // + // M+ = p1 + p2 * 2^e + // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e + // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e + // = d[k-1] * 10^(k-1) + ( rest) * 2^e + // + // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) + // + // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] + // + // but stop as soon as + // + // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e + + int n = k; + while (n > 0) + { + // Invariants: + // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) + // pow10 = 10^(n-1) <= p1 < 10^n + // + const uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) + const uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) + // + // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e + // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) + // + p1 = r; + n--; + // + // M+ = buffer * 10^n + (p1 + p2 * 2^e) + // pow10 = 10^n + // + + // Now check if enough digits have been generated. + // Compute + // + // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e + // + // Note: + // Since rest and delta share the same exponent e, it suffices to + // compare the significands. + const uint64_t rest = (uint64_t{p1} << -one.e) + p2; + if (rest <= delta) + { + // V = buffer * 10^n, with M- <= V <= M+. + + decimal_exponent += n; + + // We may now just stop. But instead look if the buffer could be + // decremented to bring V closer to w. + // + // pow10 = 10^n is now 1 ulp in the decimal representation V. + // The rounding procedure works with diyfp's with an implicit + // exponent of e. + // + // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e + // + const uint64_t ten_n = uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + // pow10 = 10^(n-1) <= p1 < 10^n + // Invariants restored. + } + + // 2) + // + // The digits of the integral part have been generated: + // + // M+ = d[k-1]...d[1]d[0] + p2 * 2^e + // = buffer + p2 * 2^e + // + // Now generate the digits of the fractional part p2 * 2^e. + // + // Note: + // No decimal point is generated: the exponent is adjusted instead. + // + // p2 actually represents the fraction + // + // p2 * 2^e + // = p2 / 2^-e + // = d[-1] / 10^1 + d[-2] / 10^2 + ... + // + // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) + // + // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m + // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) + // + // using + // + // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) + // = ( d) * 2^-e + ( r) + // + // or + // 10^m * p2 * 2^e = d + r * 2^e + // + // i.e. + // + // M+ = buffer + p2 * 2^e + // = buffer + 10^-m * (d + r * 2^e) + // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e + // + // and stop as soon as 10^-m * r * 2^e <= delta * 2^e + + assert(p2 > delta); + + int m = 0; + for (;;) + { + // Invariant: + // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e + // = buffer * 10^-m + 10^-m * (p2 ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e + // + assert(p2 <= UINT64_MAX / 10); + p2 *= 10; + const uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e + const uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e + // + // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) + // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + p2 = r; + m++; + // + // M+ = buffer * 10^-m + 10^-m * p2 * 2^e + // Invariant restored. + + // Check if enough digits have been generated. + // + // 10^-m * p2 * 2^e <= delta * 2^e + // p2 * 2^e <= 10^m * delta * 2^e + // p2 <= 10^m * delta + delta *= 10; + dist *= 10; + if (p2 <= delta) + { + break; + } + } + + // V = buffer * 10^-m, with M- <= V <= M+. + + decimal_exponent -= m; + + // 1 ulp in the decimal representation is now 10^-m. + // Since delta and dist are now scaled by 10^m, we need to do the + // same with ulp in order to keep the units in sync. + // + // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e + // + const uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // By construction this algorithm generates the shortest possible decimal + // number (Loitsch, Theorem 6.2) which rounds back to w. + // For an input number of precision p, at least + // + // N = 1 + ceil(p * log_10(2)) + // + // decimal digits are sufficient to identify all binary floating-point + // numbers (Matula, "In-and-Out conversions"). + // This implies that the algorithm does not produce more than N decimal + // digits. + // + // N = 17 for p = 53 (IEEE double precision) + // N = 9 for p = 24 (IEEE single precision) +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +inline void grisu2(char* buf, int& len, int& decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) +{ + assert(m_plus.e == m_minus.e); + assert(m_plus.e == v.e); + + // --------(-----------------------+-----------------------)-------- (A) + // m- v m+ + // + // --------------------(-----------+-----------------------)-------- (B) + // m- v m+ + // + // First scale v (and m- and m+) such that the exponent is in the range + // [alpha, gamma]. + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k + + // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + + // ----(---+---)---------------(---+---)---------------(---+---)---- + // w- w w+ + // = c*m- = c*v = c*m+ + // + // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and + // w+ are now off by a small amount. + // In fact: + // + // w - v * 10^k < 1 ulp + // + // To account for this inaccuracy, add resp. subtract 1 ulp. + // + // --------+---[---------------(---+---)---------------]---+-------- + // w- M- w M+ w+ + // + // Now any number in [M-, M+] (bounds included) will round to w when input, + // regardless of how the input rounding algorithm breaks ties. + // + // And digit_gen generates the shortest possible such number in [M-, M+]. + // Note that this does not mean that Grisu2 always generates the shortest + // possible number in the interval (m-, m+). + const diyfp M_minus(w_minus.f + 1, w_minus.e); + const diyfp M_plus (w_plus.f - 1, w_plus.e ); + + decimal_exponent = -cached.k; // = -(-k) = k + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +template +void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) +{ + static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, + "internal error: not enough precision"); + + assert(std::isfinite(value)); + assert(value > 0); + + // If the neighbors (and boundaries) of 'value' are always computed for double-precision + // numbers, all float's can be recovered using strtod (and strtof). However, the resulting + // decimal representations are not exactly "short". + // + // The documentation for 'std::to_chars' (http://en.cppreference.com/w/cpp/utility/to_chars) + // says "value is converted to a string as if by std::sprintf in the default ("C") locale" + // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' + // does. + // On the other hand, the documentation for 'std::to_chars' requires that "parsing the + // representation using the corresponding std::from_chars function recovers value exactly". That + // indicates that single precision floating-point numbers should be recovered using + // 'std::strtof'. + // + // NB: If the neighbors are computed for single-precision numbers, there is a single float + // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision + // value is off by 1 ulp. +#if 0 + const boundaries w = compute_boundaries(static_cast(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +/*! +@brief appends a decimal representation of e to buf +@return a pointer to the element following the exponent. +@pre -1000 < e < 1000 +*/ +inline char* append_exponent(char* buf, int e) +{ + assert(e > -1000); + assert(e < 1000); + + if (e < 0) + { + e = -e; + *buf++ = '-'; + } + else + { + *buf++ = '+'; + } + + uint32_t k = static_cast(e); + if (k < 10) + { + // Always print at least two digits in the exponent. + // This is for compatibility with printf("%g"). + *buf++ = '0'; + *buf++ = static_cast('0' + k); + } + else if (k < 100) + { + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + else + { + *buf++ = static_cast('0' + k / 100); + k %= 100; + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + + return buf; +} + +/*! +@brief prettify v = buf * 10^decimal_exponent + +If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point +notation. Otherwise it will be printed in exponential notation. + +@pre min_exp < 0 +@pre max_exp > 0 +*/ +inline char* format_buffer(char* buf, int len, int decimal_exponent, + int min_exp, int max_exp) +{ + assert(min_exp < 0); + assert(max_exp > 0); + + const int k = len; + const int n = len + decimal_exponent; + + // v = buf * 10^(n-k) + // k is the length of the buffer (number of decimal digits) + // n is the position of the decimal point relative to the start of the buffer. + + if (k <= n and n <= max_exp) + { + // digits[000] + // len <= max_exp + 2 + + std::memset(buf + k, '0', static_cast(n - k)); + // Make it look like a floating-point number (#362, #378) + buf[n + 0] = '.'; + buf[n + 1] = '0'; + return buf + (n + 2); + } + + if (0 < n and n <= max_exp) + { + // dig.its + // len <= max_digits10 + 1 + + assert(k > n); + + std::memmove(buf + (n + 1), buf + n, static_cast(k - n)); + buf[n] = '.'; + return buf + (k + 1); + } + + if (min_exp < n and n <= 0) + { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + -n), buf, static_cast(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast(-n)); + return buf + (2 + (-n) + k); + } + + if (k == 1) + { + // dE+123 + // len <= 1 + 5 + + buf += 1; + } + else + { + // d.igitsE+123 + // len <= max_digits10 + 1 + 5 + + std::memmove(buf + 2, buf + 1, static_cast(k - 1)); + buf[1] = '.'; + buf += 1 + k; + } + + *buf++ = 'e'; + return append_exponent(buf, n - 1); +} + +} // namespace dtoa_impl + +/*! +@brief generates a decimal representation of the floating-point number value in [first, last). + +The format of the resulting decimal representation is similar to printf's %g +format. Returns an iterator pointing past-the-end of the decimal representation. + +@note The input number must be finite, i.e. NaN's and Inf's are not supported. +@note The buffer must be large enough. +@note The result is NOT null-terminated. +*/ +template +char* to_chars(char* first, char* last, FloatType value) +{ + static_cast(last); // maybe unused - fix warning + assert(std::isfinite(value)); + + // Use signbit(value) instead of (value < 0) since signbit works for -0. + if (std::signbit(value)) + { + value = -value; + *first++ = '-'; + } + + if (value == 0) // +-0 + { + *first++ = '0'; + // Make it look like a floating-point number (#362, #378) + *first++ = '.'; + *first++ = '0'; + return first; + } + + assert(last - first >= std::numeric_limits::max_digits10); + + // Compute v = buffer * 10^decimal_exponent. + // The decimal digits are stored in the buffer, which needs to be interpreted + // as an unsigned decimal integer. + // len is the length of the buffer, i.e. the number of decimal digits. + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + + assert(len <= std::numeric_limits::max_digits10); + + // Format the buffer like printf("%.*g", prec, value) + constexpr int kMinExp = -4; + // Use digits10 here to increase compatibility with version 2. + constexpr int kMaxExp = std::numeric_limits::digits10; + + assert(last - first >= kMaxExp + 2); + assert(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); + assert(last - first >= std::numeric_limits::max_digits10 + 6); + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} + +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// serialization // +/////////////////// + +template +class serializer +{ + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + */ + serializer(output_adapter_t s, const char ichar) + : o(std::move(s)), loc(std::localeconv()), + thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), + decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)), + indent_char(ichar), indent_string(512, indent_char) + {} + + // delete because of pointer members + serializer(const serializer&) = delete; + serializer& operator=(const serializer&) = delete; + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(const BasicJsonType& val, const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) + { + switch (val.m_type) + { + case value_t::object: + { + if (val.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + + o->write_character('}'); + } + + return; + } + + case value_t::array: + { + if (val.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (pretty_print) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + + o->write_character(']'); + } + + return; + } + + case value_t::string: + { + o->write_character('\"'); + dump_escaped(*val.m_value.string, ensure_ascii); + o->write_character('\"'); + return; + } + + case value_t::boolean: + { + if (val.m_value.boolean) + { + o->write_characters("true", 4); + } + else + { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: + { + dump_integer(val.m_value.number_integer); + return; + } + + case value_t::number_unsigned: + { + dump_integer(val.m_value.number_unsigned); + return; + } + + case value_t::number_float: + { + dump_float(val.m_value.number_float); + return; + } + + case value_t::discarded: + { + o->write_characters("", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + } + } + + private: + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(const string_t& s, const bool ensure_ascii) + { + uint32_t codepoint; + uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer + + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) + { + case UTF8_ACCEPT: // decode found a new code point + { + switch (codepoint) + { + case 0x08: // backspace + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } + + case 0x09: // horizontal tab + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast(codepoint)); + bytes += 6; + } + else + { + std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast(0xD7C0 + (codepoint >> 10)), + static_cast(0xDC00 + (codepoint & 0x3FF))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + ss.str())); + } + + default: // decode found yet incomplete multi-byte code point + { + if (not ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; + } + break; + } + } + } + + if (JSON_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o->write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(static_cast(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + ss.str())); + } + } + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template::value or + std::is_same::value, + int> = 0> + void dump_integer(NumberType x) + { + // special case for "0" + if (x == 0) + { + o->write_character('0'); + return; + } + + const bool is_negative = (x <= 0) and (x != 0); // see issue #755 + std::size_t i = 0; + + while (x != 0) + { + // spare 1 byte for '\0' + assert(i < number_buffer.size() - 1); + + const auto digit = std::labs(static_cast(x % 10)); + number_buffer[i++] = static_cast('0' + digit); + x /= 10; + } + + if (is_negative) + { + // make sure there is capacity for the '-' + assert(i < number_buffer.size() - 2); + number_buffer[i++] = '-'; + } + + std::reverse(number_buffer.begin(), number_buffer.begin() + i); + o->write_characters(number_buffer.data(), i); + } + + /*! + @brief dump a floating-point number + + Dump a given floating-point number to output stream @a o. Works internally + with @a number_buffer. + + @param[in] x floating-point number to dump + */ + void dump_float(number_float_t x) + { + // NaN / inf + if (not std::isfinite(x)) + { + o->write_characters("null", 4); + return; + } + + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if == . + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 24 and std::numeric_limits::max_exponent == 128) or + (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 53 and std::numeric_limits::max_exponent == 1024); + + dump_float(x, std::integral_constant()); + } + + void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + char* begin = number_buffer.data(); + char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o->write_characters(begin, static_cast(end - begin)); + } + + void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits::max_digits10; + + // the actual conversion + std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + // negative value indicates an error + assert(len > 0); + // check if buffer was large enough + assert(static_cast(len) < number_buffer.size()); + + // erase thousands separator + if (thousands_sep != '\0') + { + const auto end = std::remove(number_buffer.begin(), + number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + assert((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } + + // convert decimal point to '.' + if (decimal_point != '\0' and decimal_point != '.') + { + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if (dec_pos != number_buffer.end()) + { + *dec_pos = '.'; + } + } + + o->write_characters(number_buffer.data(), static_cast(len)); + + // determine if need to append ".0" + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) + { + return (c == '.' or c == 'e'); + }); + + if (value_is_int_like) + { + o->write_characters(".0", 2); + } + } + + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept + { + static const std::array utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 + } + }; + + const uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6) + : static_cast(0xff >> type) & (byte); + + state = utf8d[256u + state * 16u + type]; + return state; + } + + private: + /// the output of the serializer + output_adapter_t o = nullptr; + + /// a (hopefully) large enough character buffer + std::array number_buffer{{}}; + + /// the locale + const std::lconv* loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; + + /// string buffer + std::array string_buffer{{}}; + + /// the indentation character + const char indent_char; + /// the indentation string + string_t indent_string; +}; +} +} + +// #include + + +#include +#include + +namespace nlohmann +{ +namespace detail +{ +template +class json_ref +{ + public: + using value_type = BasicJsonType; + + json_ref(value_type&& value) + : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true) + {} + + json_ref(const value_type& value) + : value_ref(const_cast(&value)), is_rvalue(false) + {} + + json_ref(std::initializer_list init) + : owned_value(init), value_ref(&owned_value), is_rvalue(true) + {} + + template + json_ref(Args&& ... args) + : owned_value(std::forward(args)...), value_ref(&owned_value), is_rvalue(true) + {} + + // class should be movable only + json_ref(json_ref&&) = default; + json_ref(const json_ref&) = delete; + json_ref& operator=(const json_ref&) = delete; + + value_type moved_or_copied() const + { + if (is_rvalue) + { + return std::move(*value_ref); + } + return *value_ref; + } + + value_type const& operator*() const + { + return *static_cast(value_ref); + } + + value_type const* operator->() const + { + return static_cast(value_ref); + } + + private: + mutable value_type owned_value = nullptr; + value_type* value_ref = nullptr; + const bool is_rvalue; +}; +} +} + +// #include + + +#include // assert +#include // accumulate +#include // string +#include // vector + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +template +class json_pointer +{ + // allow basic_json to access private members + NLOHMANN_BASIC_JSON_TPL_DECLARATION + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the empty + string is assumed which references the whole JSON value + + @throw parse_error.107 if the given JSON pointer @a s is nonempty and does + not begin with a slash (`/`); see example below + + @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is + not followed by `0` (representing `~`) or `1` (representing `/`); see + example below + + @liveexample{The example shows the construction several valid JSON pointers + as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`., + json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const noexcept + { + return std::accumulate(reference_tokens.begin(), reference_tokens.end(), + std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + escape(b); + }); + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + /*! + @param[in] s reference token to be converted into an array index + + @return integer representation of @a s + + @throw out_of_range.404 if string @a s could not be converted to an integer + */ + static int array_index(const std::string& s) + { + std::size_t processed_chars = 0; + const int res = std::stoi(s, &processed_chars); + + // check if the string was completely read + if (JSON_UNLIKELY(processed_chars != s.size())) + { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); + } + + return res; + } + + private: + /*! + @brief remove and return last reference pointer + @throw out_of_range.405 if JSON pointer has no parent + */ + std::string pop_back() + { + if (JSON_UNLIKELY(is_root())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; + } + + /// return whether pointer points to the root document + bool is_root() const + { + return reference_tokens.empty(); + } + + json_pointer top() const + { + if (JSON_UNLIKELY(is_root())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + + @throw parse_error.109 if array index is not a number + @throw type_error.313 if value cannot be unflattened + */ + BasicJsonType& get_and_create(BasicJsonType& j) const + { + using size_type = typename BasicJsonType::size_type; + auto result = &j; + + // in case no reference tokens exist, return a reference to the JSON value + // j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case detail::value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + JSON_TRY + { + result = &result->operator[](static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + single value; that is, with an empty list of reference tokens. + */ + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries to + create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_unchecked(BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->m_type == detail::value_t::null) + { + // check if reference token is a number + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const char x) + { + return (x >= '0' and x <= '9'); + }); + + // change value to array for numbers or "-" or to object otherwise + *ptr = (nums or reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; + } + + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + if (reference_token == "-") + { + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_checked(BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" cannot be used for const access + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // use unchecked array access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_checked(const BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @brief split the string input to reference tokens + + @note This function is only called by the json_pointer constructor. + All exceptions below are documented there. + + @throw parse_error.107 if the pointer is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' + */ + static std::vector split(const std::string& reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (JSON_UNLIKELY(reference_string[0] != '/')) + { + JSON_THROW(detail::parse_error::create(107, 1, + "JSON pointer must be empty or begin with '/' - was: '" + + reference_string + "'")); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + std::size_t slash = reference_string.find_first_of('/', 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == string::npos+1 = 0 + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = slash + 1, + // find next slash + slash = reference_string.find_first_of('/', start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (std::size_t pos = reference_token.find_first_of('~'); + pos != std::string::npos; + pos = reference_token.find_first_of('~', pos + 1)) + { + assert(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (JSON_UNLIKELY(pos == reference_token.size() - 1 or + (reference_token[pos + 1] != '0' and + reference_token[pos + 1] != '1'))) + { + JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); + } + } + + // finally, store the reference token + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + /*! + @brief replace all occurrences of a substring by another string + + @param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t + @param[in] f the substring to replace with @a t + @param[in] t the string to replace @a f + + @pre The search string @a f must not be empty. **This precondition is + enforced with an assertion.** + + @since version 2.0.0 + */ + static void replace_substring(std::string& s, const std::string& f, + const std::string& t) + { + assert(not f.empty()); + 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 + {} + } + + /// escape "~"" to "~0" and "/" to "~1" + static std::string escape(std::string s) + { + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + /// unescape "~1" to tilde and "~0" to slash (order is important!) + static void unescape(std::string& s) + { + replace_substring(s, "~1", "/"); + replace_substring(s, "~0", "~"); + } + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const std::string& reference_string, + const BasicJsonType& value, + BasicJsonType& result) + { + switch (value.m_type) + { + case detail::value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (std::size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case detail::value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + + @throw parse_error.109 if array index is not a number + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + @throw type_error.313 if value cannot be unflattened + */ + static BasicJsonType + unflatten(const BasicJsonType& value) + { + if (JSON_UNLIKELY(not value.is_object())) + { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); + } + + BasicJsonType result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (JSON_UNLIKELY(not element.second.is_primitive())) + { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); + } + + // assign value to reference pointed to by JSON pointer; Note that if + // the JSON pointer is "" (i.e., points to the whole value), function + // get_and_create returns a reference to result itself. An assignment + // will then create a primitive value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + friend bool operator==(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return (lhs.reference_tokens == rhs.reference_tokens); + } + + friend bool operator!=(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return not (lhs == rhs); + } + + /// the reference tokens + std::vector reference_tokens; +}; +} + +// #include + + +#include + +// #include + +// #include + + +namespace nlohmann +{ +template +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @param[in] j JSON value to read from + @param[in,out] val value to write to + */ + template + static void from_json(BasicJsonType&& j, ValueType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward(j), val))) + { + ::nlohmann::from_json(std::forward(j), val); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref basic_json + class. + + @param[in,out] j JSON value to write to + @param[in] val value to read from + */ + template + static void to_json(BasicJsonType& j, ValueType&& val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward(val)))) + { + ::nlohmann::to_json(j, std::forward(val)); + } +}; +} + + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ + +/*! +@brief a class to store JSON values + +@tparam ObjectType type for JSON objects (`std::map` by default; will be used +in @ref object_t) +@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used +in @ref array_t) +@tparam StringType type for JSON strings and object keys (`std::string` by +default; will be used in @ref string_t) +@tparam BooleanType type for JSON booleans (`bool` by default; will be used +in @ref boolean_t) +@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by +default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) +@tparam NumberFloatType type for JSON floating-point numbers (`double` by +default; will be used in @ref number_float_t) +@tparam AllocatorType type of the allocator to use (`std::allocator` by +default) +@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` +and `from_json()` (@ref adl_serializer by default) + +@requirement The class satisfies the following concept requirements: +- Basic + - [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible): + JSON values can be default constructed. The result will be a JSON null + value. + - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): + A JSON value can be constructed from an rvalue argument. + - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): + A JSON value can be copy-constructed from an lvalue expression. + - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): + A JSON value van be assigned from an rvalue argument. + - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): + A JSON value can be copy-assigned from an lvalue expression. + - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible): + JSON values can be destructed. +- Layout + - [StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType): + JSON values have + [standard layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout): + All non-static data members are private and standard layout types, the + class has no virtual functions or (virtual) base classes. +- Library-wide + - [EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable): + JSON values can be compared with `==`, see @ref + operator==(const_reference,const_reference). + - [LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable): + JSON values can be compared with `<`, see @ref + operator<(const_reference,const_reference). + - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable): + Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of + other compatible types, using unqualified function call @ref swap(). + - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer): + JSON values can be compared against `std::nullptr_t` objects which are used + to model the `null` value. +- Container + - [Container](http://en.cppreference.com/w/cpp/concept/Container): + JSON values can be used like STL containers and provide iterator access. + - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer); + JSON values can be used like STL containers and provide reverse iterator + access. + +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@internal +@note ObjectType trick from http://stackoverflow.com/a/9860911 +@endinternal + +@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange +Format](http://rfc7159.net/rfc7159) + +@since version 1.0.0 + +@nosubgrouping +*/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +class basic_json +{ + private: + template friend struct detail::external_constructor; + friend ::nlohmann::json_pointer; + friend ::nlohmann::detail::parser; + friend ::nlohmann::detail::serializer; + template + friend class ::nlohmann::detail::iter_impl; + template + friend class ::nlohmann::detail::binary_writer; + template + friend class ::nlohmann::detail::binary_reader; + + /// workaround type for MSVC + using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + + // convenience aliases for types residing in namespace detail; + using lexer = ::nlohmann::detail::lexer; + using parser = ::nlohmann::detail::parser; + + using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; + template + using internal_iterator = ::nlohmann::detail::internal_iterator; + template + using iter_impl = ::nlohmann::detail::iter_impl; + template + using iteration_proxy = ::nlohmann::detail::iteration_proxy; + template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; + + template + using output_adapter_t = ::nlohmann::detail::output_adapter_t; + + using binary_reader = ::nlohmann::detail::binary_reader; + template using binary_writer = ::nlohmann::detail::binary_writer; + + using serializer = ::nlohmann::detail::serializer; + + public: + using value_t = detail::value_t; + /// @copydoc nlohmann::json_pointer + using json_pointer = ::nlohmann::json_pointer; + template + using json_serializer = JSONSerializer; + /// helper type for initializer lists of basic_json values + using initializer_list_t = std::initializer_list>; + + //////////////// + // exceptions // + //////////////// + + /// @name exceptions + /// Classes to implement user-defined exceptions. + /// @{ + + /// @copydoc detail::exception + using exception = detail::exception; + /// @copydoc detail::parse_error + using parse_error = detail::parse_error; + /// @copydoc detail::invalid_iterator + using invalid_iterator = detail::invalid_iterator; + /// @copydoc detail::type_error + using type_error = detail::type_error; + /// @copydoc detail::out_of_range + using out_of_range = detail::out_of_range; + /// @copydoc detail::other_error + using other_error = detail::other_error; + + /// @} + + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType; + + /// the type of an element pointer + using pointer = typename std::allocator_traits::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits::const_pointer; + + /// an iterator for a basic_json container + using iterator = iter_impl; + /// a const iterator for a basic_json container + using const_iterator = iter_impl; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator; + + /// @} + + + /*! + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() + { + return allocator_type(); + } + + /*! + @brief returns version information on the library + + This function returns a JSON object with information about the library, + including the version number and information on the platform and compiler. + + @return JSON object holding version information + key | description + ----------- | --------------- + `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). + `copyright` | The copyright line for the library as string. + `name` | The name of the library as string. + `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. + `url` | The URL of the project as string. + `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string). + + @liveexample{The following code shows an example output of the `meta()` + function.,meta} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @complexity Constant. + + @since 2.1.0 + */ + static basic_json meta() + { + basic_json result; + + result["copyright"] = "(C) 2013-2017 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["url"] = "https://github.com/nlohmann/json"; + result["version"]["string"] = + std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_PATCH); + result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; + result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; + result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; + +#ifdef _WIN32 + result["platform"] = "win32"; +#elif defined __linux__ + result["platform"] = "linux"; +#elif defined __APPLE__ + result["platform"] = "apple"; +#elif defined __unix__ + result["platform"] = "unix"; +#else + result["platform"] = "unknown"; +#endif + +#if defined(__ICC) || defined(__INTEL_COMPILER) + result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; +#elif defined(__clang__) + result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; +#elif defined(__GNUC__) || defined(__GNUG__) + result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; +#elif defined(__HP_cc) || defined(__HP_aCC) + result["compiler"] = "hp" +#elif defined(__IBMCPP__) + result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; +#elif defined(_MSC_VER) + result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; +#elif defined(__PGI) + result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; +#elif defined(__SUNPRO_CC) + result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; +#else + result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; +#endif + +#ifdef __cplusplus + result["compiler"]["c++"] = std::to_string(__cplusplus); +#else + result["compiler"]["c++"] = "unknown"; +#endif + return result; + } + + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + +#if defined(JSON_HAS_CPP_14) + // Use transparent comparator if possible, combined with perfect forwarding + // on find() and count() calls prevents unnecessary string construction. + using object_comparator_t = std::less<>; +#else + using object_comparator_t = std::less; +#endif + + /*! + @brief a type for an object + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: + > An object is an unordered collection of zero or more name/value pairs, + > where a name is a string and a value is a string, number, boolean, null, + > object, or array. + + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) + + #### Default type + + With the default values for @a ObjectType (`std::map`), @a StringType + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: + + @code {.cpp} + std::map< + std::string, // key_type + basic_json, // value_type + std::less, // key_compare + std::allocator> // allocator_type + > + @endcode + + #### Behavior + + The choice of @a object_t influences the behavior of the JSON class. With + the default type, objects have the following behavior: + + - When all names are unique, objects will be interoperable in the sense + that all software implementations receiving that object will agree on + the name-value mappings. + - When the names within an object are not unique, it is unspecified which + one of the values for a given key will be chosen. For instance, + `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or + `{"key": 2}`. + - Internally, name/value pairs are stored in lexicographical order of the + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. + - When comparing objects, the order of the name/value pairs is irrelevant. + This makes objects interoperable in the sense that they will not be + affected by these differences. For instance, `{"b": 1, "a": 2}` and + `{"a": 2, "b": 1}` will be treated as equal. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the object's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. + + #### Storage + + Objects are stored as pointers in a @ref basic_json type. That is, for any + access to object values, a pointer of type `object_t*` must be + dereferenced. + + @sa @ref array_t -- type for an array value + + @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. + */ + using object_t = ObjectType>>; + + /*! + @brief a type for an array + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: + > An array is an ordered sequence of zero or more values. + + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) + + #### Default type + + With the default values for @a ArrayType (`std::vector`) and @a + AllocatorType (`std::allocator`), the default value for @a array_t is: + + @code {.cpp} + std::vector< + basic_json, // value_type + std::allocator // allocator_type + > + @endcode + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the array's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. + + #### Storage + + Arrays are stored as pointers in a @ref basic_json type. That is, for any + access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa @ref object_t -- type for an object value + + @since version 1.0.0 + */ + using array_t = ArrayType>; + + /*! + @brief a type for a string + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: + > A string is a sequence of zero or more Unicode characters. + + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. + + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. + + #### Default type + + With the default values for @a StringType (`std::string`), the default + value for @a string_t is: + + @code {.cpp} + std::string + @endcode + + #### Encoding + + Strings are stored in UTF-8 encoding. Therefore, functions like + `std::string::size()` or `std::string::length()` return the number of + bytes in the string rather than the number of characters or glyphs. + + #### String comparison + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > Software implementations are typically required to test names of object + > members for equality. Implementations that transform the textual + > representation into sequences of Unicode code units and then perform the + > comparison numerically, code unit by code unit, are interoperable in the + > sense that implementations will agree in all cases on equality or + > inequality of two strings. For example, implementations that compare + > strings with escaped characters unconverted may incorrectly find that + > `"a\\b"` and `"a\u005Cb"` are not equal. + + This implementation is interoperable as it does compare strings code unit + by code unit. + + #### Storage + + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be + dereferenced. + + @since version 1.0.0 + */ + using string_t = StringType; + + /*! + @brief a type for a boolean + + [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a + type which differentiates the two literals `true` and `false`. + + To store objects in C++, a type is defined by the template parameter @a + BooleanType which chooses the type to use. + + #### Default type + + With the default values for @a BooleanType (`bool`), the default value for + @a boolean_t is: + + @code {.cpp} + bool + @endcode + + #### Storage + + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 + */ + using boolean_t = BooleanType; + + /*! + @brief a type for a number (integer) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store integer numbers in C++, a type is defined by the template + parameter @a NumberIntegerType which chooses the type to use. + + #### Default type + + With the default values for @a NumberIntegerType (`int64_t`), the default + value for @a number_integer_t is: + + @code {.cpp} + int64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `9223372036854775807` (INT64_MAX) and the minimal integer number + that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange of the exactly supported range [INT64_MIN, + INT64_MAX], this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_integer_t = NumberIntegerType; + + /*! + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], + this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + + /*! + @brief a type for a number (floating-point) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store floating-point numbers in C++, a type is defined by the template + parameter @a NumberFloatType which chooses the type to use. + + #### Default type + + With the default values for @a NumberFloatType (`double`), the default + value for @a number_float_t is: + + @code {.cpp} + double + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ + floating-point literal `01.2` will be serialized to `1.2`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > This specification allows implementations to set limits on the range and + > precision of numbers accepted. Since software that implements IEEE + > 754-2008 binary64 (double precision) numbers is generally available and + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected + > precision. + + This implementation does exactly follow this approach, as it uses double + precision floating-point numbers. Note values smaller than + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` + will be stored as NaN internally and be serialized to `null`. + + #### Storage + + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa @ref number_integer_t -- type for number values (integer) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_float_t = NumberFloatType; + + /// @} + + private: + + /// helper for exception-safe object creation + template + static T* create(Args&& ... args) + { + AllocatorType alloc; + using AllocatorTraits = std::allocator_traits>; + + auto deleter = [&](T * object) + { + AllocatorTraits::deallocate(alloc, object, 1); + }; + std::unique_ptr object(AllocatorTraits::allocate(alloc, 1), deleter); + AllocatorTraits::construct(alloc, object.get(), std::forward(args)...); + assert(object != nullptr); + return object.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create(); + break; + } + + case value_t::array: + { + array = create(); + break; + } + + case value_t::string: + { + string = create(""); + break; + } + + case value_t::boolean: + { + boolean = boolean_t(false); + break; + } + + case value_t::number_integer: + { + number_integer = number_integer_t(0); + break; + } + + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } + + case value_t::number_float: + { + number_float = number_float_t(0.0); + break; + } + + case value_t::null: + { + object = nullptr; // silence warning, see #821 + break; + } + + default: + { + object = nullptr; // silence warning, see #821 + if (JSON_UNLIKELY(t == value_t::null)) + { + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.1.1")); // LCOV_EXCL_LINE + } + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) + { + string = create(value); + } + + /// constructor for rvalue strings + json_value(string_t&& value) + { + string = create(std::move(value)); + } + + /// constructor for objects + json_value(const object_t& value) + { + object = create(value); + } + + /// constructor for rvalue objects + json_value(object_t&& value) + { + object = create(std::move(value)); + } + + /// constructor for arrays + json_value(const array_t& value) + { + array = create(value); + } + + /// constructor for rvalue arrays + json_value(array_t&& value) + { + array = create(std::move(value)); + } + + void destroy(value_t t) noexcept + { + switch (t) + { + case value_t::object: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, object); + std::allocator_traits::deallocate(alloc, object, 1); + break; + } + + case value_t::array: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, array); + std::allocator_traits::deallocate(alloc, array, 1); + break; + } + + case value_t::string: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, string); + std::allocator_traits::deallocate(alloc, string, 1); + break; + } + + default: + { + break; + } + } + } + }; + + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + */ + void assert_invariant() const noexcept + { + assert(m_type != value_t::object or m_value.object != nullptr); + assert(m_type != value_t::array or m_value.array != nullptr); + assert(m_type != value_t::string or m_value.string != nullptr); + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /*! + @brief parser event types + + The parser callback distinguishes the following events: + - `object_start`: the parser read `{` and started to process a JSON object + - `key`: the parser read a key of a value in an object + - `object_end`: the parser read `}` and finished processing a JSON object + - `array_start`: the parser read `[` and started to process a JSON array + - `array_end`: the parser read `]` and finished processing a JSON array + - `value`: the parser finished reading a JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + @sa @ref parser_callback_t for more information and examples + */ + using parse_event_t = typename parser::parse_event_t; + + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse, it is called on certain events + (passed as @ref parse_event_t via parameter @a event) with a set recursion + depth @a depth and context JSON value @a parsed. The return value of the + callback function is a boolean indicating whether the element that emitted + the callback shall be kept or not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa @ref parse for examples + + @since version 1.0.0 + */ + using parser_callback_t = typename parser::parser_callback_t; + + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /*! + @brief create an empty value with a given type + + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @param[in] v the type of the value to create + + @complexity Constant. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @sa @ref clear() -- restores the postcondition of this constructor + + @since version 1.0.0 + */ + basic_json(const value_t v) + : m_type(v), m_value(v) + { + assert_invariant(); + } + + /*! + @brief create a null object + + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} + + @since version 1.0.0 + */ + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) + { + assert_invariant(); + } + + /*! + @brief create a JSON value + + This is a "catch all" constructor for all compatible JSON types; that is, + types for which a `to_json()` method exists. The constructor forwards the + parameter @a val to that method (to `json_serializer::to_json` method + with `U = uncvref_t`, to be exact). + + Template type @a CompatibleType includes, but is not limited to, the + following types: + - **arrays**: @ref array_t and all kinds of compatible containers such as + `std::vector`, `std::deque`, `std::list`, `std::forward_list`, + `std::array`, `std::valarray`, `std::set`, `std::unordered_set`, + `std::multiset`, and `std::unordered_multiset` with a `value_type` from + which a @ref basic_json value can be constructed. + - **objects**: @ref object_t and all kinds of compatible associative + containers such as `std::map`, `std::unordered_map`, `std::multimap`, + and `std::unordered_multimap` with a `key_type` compatible to + @ref string_t and a `value_type` from which a @ref basic_json value can + be constructed. + - **strings**: @ref string_t, string literals, and all compatible string + containers can be used. + - **numbers**: @ref number_integer_t, @ref number_unsigned_t, + @ref number_float_t, and all convertible number types such as `int`, + `size_t`, `int64_t`, `float` or `double` can be used. + - **boolean**: @ref boolean_t / `bool` can be used. + + See the examples below. + + @tparam CompatibleType a type such that: + - @a CompatibleType is not derived from `std::istream`, + - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move + constructors), + - @a CompatibleType is not a @ref basic_json nested type (e.g., + @ref json_pointer, @ref iterator, etc ...) + - @ref @ref json_serializer has a + `to_json(basic_json_t&, CompatibleType&&)` method + + @tparam U = `uncvref_t` + + @param[in] val the value to be forwarded to the respective constructor + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @liveexample{The following code shows the constructor with several + compatible types.,basic_json__CompatibleType} + + @since version 2.1.0 + */ + template , + detail::enable_if_t< + detail::is_compatible_type::value, int> = 0> + basic_json(CompatibleType && val) noexcept(noexcept( + JSONSerializer::to_json(std::declval(), + std::forward(val)))) + { + JSONSerializer::to_json(*this, std::forward(val)); + assert_invariant(); + } + + /*! + @brief create a container (array or object) from an initializer list + + Creates a JSON value of type array or object from the passed initializer + list @a init. In case @a type_deduction is `true` (default), the type of + the JSON value to be created is deducted from the initializer list @a init + according to the following rules: + + 1. If the list is empty, an empty JSON object value `{}` is created. + 2. If the list consists of pairs whose first element is a string, a JSON + object value is created where the first elements of the pairs are + treated as keys and the second elements are as values. + 3. In all other cases, an array is created. + + The rules aim to create the best fit between a C++ initializer list and + JSON values. The rationale is as follows: + + 1. The empty initializer list is written as `{}` which is exactly an empty + JSON object. + 2. C++ has no way of describing mapped types other than to list a list of + pairs. As JSON requires that keys must be of type string, rule 2 is the + weakest constraint one can pose on initializer lists to interpret them + as an object. + 3. In all other cases, the initializer list could not be interpreted as + JSON object type, so interpreting it as JSON array type is safe. + + With the rules described above, the following JSON values cannot be + expressed by an initializer list: + + - the empty array (`[]`): use @ref array(initializer_list_t) + with an empty initializer list in this case + - arrays whose elements satisfy rule 2: use @ref + array(initializer_list_t) with the same initializer list + in this case + + @note When used without parentheses around an empty initializer list, @ref + basic_json() is called instead of this function, yielding the JSON null + value. + + @param[in] init initializer list with JSON values + + @param[in] type_deduction internal parameter; when set to `true`, the type + of the JSON value is deducted from the initializer list @a init; when set + to `false`, the type provided via @a manual_type is forced. This mode is + used by the functions @ref array(initializer_list_t) and + @ref object(initializer_list_t). + + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref + value_t::array and @ref value_t::object are valid); when @a type_deduction + is set to `true`, this parameter has no effect + + @throw type_error.301 if @a type_deduction is `false`, @a manual_type is + `value_t::object`, but @a init contains an element which is not a pair + whose first element is a string. In this case, the constructor could not + create an object. If @a type_deduction would have be `true`, an array + would have been created. See @ref object(initializer_list_t) + for an example. + + @complexity Linear in the size of the initializer list @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows how JSON values are created from + initializer lists.,basic_json__list_init_t} + + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + basic_json(initializer_list_t init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const detail::json_ref& element_ref) + { + return (element_ref->is_array() and element_ref->size() == 2 and (*element_ref)[0].is_string()); + }); + + // adjust type if type deduction is not wanted + if (not type_deduction) + { + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if object is wanted but impossible, throw an exception + if (JSON_UNLIKELY(manual_type == value_t::object and not is_an_object)) + { + JSON_THROW(type_error::create(301, "cannot create object from initializer list")); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create object + m_type = value_t::object; + m_value = value_t::object; + + std::for_each(init.begin(), init.end(), [this](const detail::json_ref& element_ref) + { + auto element = element_ref.moved_or_copied(); + m_value.object->emplace( + std::move(*((*element.m_value.array)[0].m_value.string)), + std::move((*element.m_value.array)[1])); + }); + } + else + { + // the initializer list describes an array -> create array + m_type = value_t::array; + m_value.array = create(init.begin(), init.end()); + } + + assert_invariant(); + } + + /*! + @brief explicitly create an array from an initializer list + + Creates a JSON array value from a given initializer list. That is, given a + list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the + initializer list is empty, the empty array `[]` is created. + + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref + basic_json(initializer_list_t, bool, value_t)). These cases + are: + 1. creating an array whose elements are all pairs whose first element is a + string -- in this case, the initializer list constructor would create an + object, taking the first elements as keys + 2. creating an empty array -- passing the empty initializer list to the + initializer list constructor yields an empty object + + @param[in] init initializer list with JSON values to create an array from + (optional) + + @return JSON array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `array` + function.,array} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + static basic_json array(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::array); + } + + /*! + @brief explicitly create an object from an initializer list + + Creates a JSON object value from a given initializer list. The initializer + lists elements must be pairs, and their first elements must be strings. If + the initializer list is empty, the empty object `{}` is created. + + @note This function is only added for symmetry reasons. In contrast to the + related function @ref array(initializer_list_t), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor @ref basic_json(initializer_list_t, bool, value_t). + + @param[in] init initializer list to create an object from (optional) + + @return JSON object value + + @throw type_error.301 if @a init is not a list of pairs whose first + elements are strings. In this case, no object can be created. When such a + value is passed to @ref basic_json(initializer_list_t, bool, value_t), + an array would have been created from the passed initializer list @a init. + See example below. + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `object` + function.,object} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + + @since version 1.0.0 + */ + static basic_json object(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::object); + } + + /*! + @brief construct an array with count copies of given value + + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. + + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy + + @post `std::distance(begin(),end()) == cnt` holds. + + @complexity Linear in @a cnt. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows examples for the @ref + basic_json(size_type\, const basic_json&) + constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 + */ + basic_json(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create(cnt, val); + assert_invariant(); + } + + /*! + @brief construct a JSON container given an iterator range + + Constructs the JSON value with the contents of the range `[first, last)`. + The semantics depends on the different types a JSON value can have: + - In case of a null type, invalid_iterator.206 is thrown. + - In case of other primitive types (number, boolean, or string), @a first + must be `begin()` and @a last must be `end()`. In this case, the value is + copied. Otherwise, invalid_iterator.204 is thrown. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector` or `std::map`; that is, a JSON array + or object is constructed from the values in the range. + + @tparam InputIT an input iterator type (@ref iterator or @ref + const_iterator) + + @param[in] first begin of the range to copy from (included) + @param[in] last end of the range to copy from (excluded) + + @pre Iterators @a first and @a last must be initialized. **This + precondition is enforced with an assertion (see warning).** If + assertions are switched off, a violation of this precondition yields + undefined behavior. + + @pre Range `[first, last)` is valid. Usually, this precondition cannot be + checked efficiently. Only certain edge cases are detected; see the + description of the exceptions below. A violation of this precondition + yields undefined behavior. + + @warning A precondition is enforced with a runtime assertion that will + result in calling `std::abort` if this precondition is not met. + Assertions can be disabled by defining `NDEBUG` at compile time. + See http://en.cppreference.com/w/cpp/error/assert for more + information. + + @throw invalid_iterator.201 if iterators @a first and @a last are not + compatible (i.e., do not belong to the same JSON value). In this case, + the range `[first, last)` is undefined. + @throw invalid_iterator.204 if iterators @a first and @a last belong to a + primitive type (number, boolean, or string), but @a first does not point + to the first element any more. In this case, the range `[first, last)` is + undefined. See example code below. + @throw invalid_iterator.206 if iterators @a first and @a last belong to a + null value. In this case, the range `[first, last)` is undefined. + + @complexity Linear in distance between @a first and @a last. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows several ways to create JSON values by + specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type = 0> + basic_json(InputIT first, InputIT last) + { + assert(first.m_object != nullptr); + assert(last.m_object != nullptr); + + // make sure iterator fits the current value + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); + } + + // copy type from first iterator + m_type = first.m_object->m_type; + + // check if iterator range is complete for primitive values + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_UNLIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + break; + } + + default: + break; + } + + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value.number_float = first.m_object->m_value.number_float; + break; + } + + case value_t::boolean: + { + m_value.boolean = first.m_object->m_value.boolean; + break; + } + + case value_t::string: + { + m_value = *first.m_object->m_value.string; + break; + } + + case value_t::object: + { + m_value.object = create(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_value.array = create(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + + std::string(first.m_object->type_name()))); + } + + assert_invariant(); + } + + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + /// @private + basic_json(const detail::json_ref& ref) + : basic_json(ref.moved_or_copied()) + {} + + /*! + @brief copy constructor + + Creates a copy of a given JSON value. + + @param[in] other the JSON value to copy + + @post `*this == other` + + @complexity Linear in the size of @a other. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - As postcondition, it holds: `other == basic_json(other)`. + + @liveexample{The following code shows an example for the copy + constructor.,basic_json__basic_json} + + @since version 1.0.0 + */ + basic_json(const basic_json& other) + : m_type(other.m_type) + { + // check of passed value is valid + other.assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + m_value = *other.m_value.object; + break; + } + + case value_t::array: + { + m_value = *other.m_value.array; + break; + } + + case value_t::string: + { + m_value = *other.m_value.string; + break; + } + + case value_t::boolean: + { + m_value = other.m_value.boolean; + break; + } + + case value_t::number_integer: + { + m_value = other.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value = other.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value = other.m_value.number_float; + break; + } + + default: + break; + } + + assert_invariant(); + } + + /*! + @brief move constructor + + Move constructor. Constructs a JSON value with the contents of the given + value @a other using move semantics. It "steals" the resources from @a + other and leaves it as JSON null value. + + @param[in,out] other value to move to this object + + @post `*this` has the same value as @a other before the call. + @post @a other is a JSON null value. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @requirement This function helps `basic_json` satisfying the + [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible) + requirements. + + @liveexample{The code below shows the move constructor explicitly called + via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 + */ + basic_json(basic_json&& other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) + { + // check that passed value is valid + other.assert_invariant(); + + // invalidate payload + other.m_type = value_t::null; + other.m_value = {}; + + assert_invariant(); + } + + /*! + @brief copy assignment + + Copy assignment operator. Copies a JSON value via the "copy and swap" + strategy: It is expressed in terms of the copy constructor, destructor, + and the `swap()` member function. + + @param[in] other value to copy from + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + + @liveexample{The code below shows and example for the copy assignment. It + creates a copy of value `a` which is then swapped with `b`. Finally\, the + copy of `a` (which is the null value after the swap) is + destroyed.,basic_json__copyassignment} + + @since version 1.0.0 + */ + reference& operator=(basic_json other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + // check that passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + + assert_invariant(); + return *this; + } + + /*! + @brief destructor + + Destroys the JSON value and frees all allocated memory. + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 + */ + ~basic_json() noexcept + { + assert_invariant(); + m_value.destroy(m_type); + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /*! + @brief serialization + + Serialization function for JSON values. The function tries to mimic + Python's `json.dumps()` function, and currently supports its @a indent + and @a ensure_ascii parameters. + + @param[in] indent If indent is nonnegative, then array elements and object + members will be pretty-printed with that indent level. An indent level of + `0` will only insert newlines. `-1` (the default) selects the most compact + representation. + @param[in] indent_char The character to use for indentation if @a indent is + greater than `0`. The default is ` ` (space). + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists + of ASCII characters only. + + @return string containing the serialization of the JSON value + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + + @complexity Linear. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @liveexample{The following example shows the effect of different @a indent\, + @a indent_char\, and @a ensure_ascii parameters to the result of the + serialization.,dump} + + @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0; indentation character @a indent_char, option + @a ensure_ascii and exceptions added in version 3.0.0 + */ + string_t dump(const int indent = -1, const char indent_char = ' ', + const bool ensure_ascii = false) const + { + string_t result; + serializer s(detail::output_adapter(result), indent_char); + + if (indent >= 0) + { + s.dump(*this, true, ensure_ascii, static_cast(indent)); + } + else + { + s.dump(*this, false, ensure_ascii, 0); + } + + return result; + } + + /*! + @brief return the type of the JSON value (explicit) + + Return the type of the JSON value as a value from the @ref value_t + enumeration. + + @return the type of the JSON value + Value type | return value + ------------------------- | ------------------------- + null | value_t::null + boolean | value_t::boolean + string | value_t::string + number (integer) | value_t::number_integer + number (unsigned integer) | value_t::number_unsigned + number (floating-point) | value_t::number_float + object | value_t::object + array | value_t::array + discarded | value_t::discarded + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `type()` for all JSON + types.,type} + + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr value_t type() const noexcept + { + return m_type; + } + + /*! + @brief return whether type is primitive + + This function returns true if and only if the JSON type is primitive + (string, number, boolean, or null). + + @return `true` if type is primitive (string, number, boolean, or null), + `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_primitive()` for all JSON + types.,is_primitive} + + @sa @ref is_structured() -- returns whether JSON value is structured + @sa @ref is_null() -- returns whether JSON value is `null` + @sa @ref is_string() -- returns whether JSON value is a string + @sa @ref is_boolean() -- returns whether JSON value is a boolean + @sa @ref is_number() -- returns whether JSON value is a number + + @since version 1.0.0 + */ + constexpr bool is_primitive() const noexcept + { + return is_null() or is_string() or is_boolean() or is_number(); + } + + /*! + @brief return whether type is structured + + This function returns true if and only if the JSON type is structured + (array or object). + + @return `true` if type is structured (array or object), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_structured()` for all JSON + types.,is_structured} + + @sa @ref is_primitive() -- returns whether value is primitive + @sa @ref is_array() -- returns whether value is an array + @sa @ref is_object() -- returns whether value is an object + + @since version 1.0.0 + */ + constexpr bool is_structured() const noexcept + { + return is_array() or is_object(); + } + + /*! + @brief return whether value is null + + This function returns true if and only if the JSON value is null. + + @return `true` if type is null, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_null()` for all JSON + types.,is_null} + + @since version 1.0.0 + */ + constexpr bool is_null() const noexcept + { + return (m_type == value_t::null); + } + + /*! + @brief return whether value is a boolean + + This function returns true if and only if the JSON value is a boolean. + + @return `true` if type is boolean, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_boolean()` for all JSON + types.,is_boolean} + + @since version 1.0.0 + */ + constexpr bool is_boolean() const noexcept + { + return (m_type == value_t::boolean); + } + + /*! + @brief return whether value is a number + + This function returns true if and only if the JSON value is a number. This + includes both integer (signed and unsigned) and floating-point values. + + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number()` for all JSON + types.,is_number} + + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number() const noexcept + { + return is_number_integer() or is_number_float(); + } + + /*! + @brief return whether value is an integer number + + This function returns true if and only if the JSON value is a signed or + unsigned integer number. This excludes floating-point values. + + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_integer()` for all + JSON types.,is_number_integer} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number_integer() const noexcept + { + return (m_type == value_t::number_integer or m_type == value_t::number_unsigned); + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true if and only if the JSON value is an unsigned + integer number. This excludes floating-point and signed integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_unsigned()` for all + JSON types.,is_number_unsigned} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + constexpr bool is_number_unsigned() const noexcept + { + return (m_type == value_t::number_unsigned); + } + + /*! + @brief return whether value is a floating-point number + + This function returns true if and only if the JSON value is a + floating-point number. This excludes signed and unsigned integer values. + + @return `true` if type is a floating-point number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_float()` for all + JSON types.,is_number_float} + + @sa @ref is_number() -- check if value is number + @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + + @since version 1.0.0 + */ + constexpr bool is_number_float() const noexcept + { + return (m_type == value_t::number_float); + } + + /*! + @brief return whether value is an object + + This function returns true if and only if the JSON value is an object. + + @return `true` if type is object, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_object()` for all JSON + types.,is_object} + + @since version 1.0.0 + */ + constexpr bool is_object() const noexcept + { + return (m_type == value_t::object); + } + + /*! + @brief return whether value is an array + + This function returns true if and only if the JSON value is an array. + + @return `true` if type is array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_array()` for all JSON + types.,is_array} + + @since version 1.0.0 + */ + constexpr bool is_array() const noexcept + { + return (m_type == value_t::array); + } + + /*! + @brief return whether value is a string + + This function returns true if and only if the JSON value is a string. + + @return `true` if type is string, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_string()` for all JSON + types.,is_string} + + @since version 1.0.0 + */ + constexpr bool is_string() const noexcept + { + return (m_type == value_t::string); + } + + /*! + @brief return whether value is discarded + + This function returns true if and only if the JSON value was discarded + during parsing with a callback function (see @ref parser_callback_t). + + @note This function will always be `false` for JSON values after parsing. + That is, discarded values can only occur during parsing, but will be + removed when inside a structured value or replaced by null in other cases. + + @return `true` if type is discarded, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_discarded()` for all JSON + types.,is_discarded} + + @since version 1.0.0 + */ + constexpr bool is_discarded() const noexcept + { + return (m_type == value_t::discarded); + } + + /*! + @brief return the type of the JSON value (implicit) + + Implicitly return the type of the JSON value as a value from the @ref + value_t enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies the @ref value_t operator for + all JSON types.,operator__value_t} + + @sa @ref type() -- return the type of the JSON value (explicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept + { + return m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get a boolean (explicit) + boolean_t get_impl(boolean_t* /*unused*/) const + { + if (JSON_LIKELY(is_boolean())) + { + return m_value.boolean; + } + + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name()))); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t* /*unused*/) noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t* /*unused*/) noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t* /*unused*/) noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This function helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw type_error.303 if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // delegate the call to get_ptr<>() + auto ptr = obj.template get_ptr::type>(); + + if (JSON_LIKELY(ptr != nullptr)) + { + return *ptr; + } + + JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name()))); + } + + public: + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template::type, basic_json_t>::value, + int> = 0> + basic_json get() const + { + return *this; + } + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value + which is [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const basic_json&, ValueType&)`, and + - @ref json_serializer does not have a `from_json()` method of + the form `ValueType from_json(const basic_json&)` + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get__ValueType_const} + + @since version 2.1.0 + */ + template, + detail::enable_if_t < + not std::is_same::value and + detail::has_from_json::value and + not detail::has_non_default_from_json::value, + int> = 0> + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), std::declval()))) + { + // we cannot static_assert on ValueTypeCV being non-const, because + // there is support for get(), which is why we + // still need the uncvref + static_assert(not std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + static_assert(std::is_default_constructible::value, + "types must be DefaultConstructible when used with get()"); + + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + } + + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer has a `from_json()` method of the form + `ValueType from_json(const basic_json&)` + + @note If @ref json_serializer has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @since version 2.1.0 + */ + template, + detail::enable_if_t::value and + detail::has_non_default_from_json::value, + int> = 0> + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval()))) + { + static_assert(not std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return JSONSerializer::from_json(*this); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get() noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template::value, int>::type = 0> + constexpr const PointerType get() const noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (implicit) + + Implicit pointer access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the pointee of the result yields an undefined + state. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. Enforced by a static + assertion. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get_ptr} + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get_ptr() noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (implicit) + @copydoc get_ptr() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + constexpr const PointerType get_ptr() const noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a reference value (implicit) + + Implicit reference access to the internally stored JSON value. No copies + are made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. Enforced by static assertion. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + type_error.303 otherwise + + @throw type_error.303 in case passed type @a ReferenceType is incompatible + with the stored JSON value; see example below + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.1.0 + */ + template::value, int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw type_error.302 in case passed type @a ValueType is incompatible + to the JSON value type (e.g., the JSON value is of type boolean, but a + string is requested); see example below + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename std::enable_if < + not std::is_pointer::value and + not std::is_same>::value and + not std::is_same::value +#ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015 + and not std::is_same>::value +#endif +#if defined(JSON_HAS_CPP_17) + and not std::is_same::value +#endif + , int >::type = 0 > + operator ValueType() const + { + // delegate the call to get<>() const + return get(); + } + + /// @} + + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__size_type} + */ + reference at(size_type idx) + { + // at only works for arrays + if (JSON_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, + with bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__size_type_const} + */ + const_reference at(size_type idx) const + { + // at only works for arrays + if (JSON_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__object_t_key_type} + */ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, + with bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__object_t_key_type_const} + */ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array or null; in that + cases, using the [] operator with an index makes no sense. + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using `[]` operator. Note the addition of `null` + values.,operatorarray__size_type} + + @since version 1.0.0 + */ + reference operator[](size_type idx) + { + // implicitly convert null value to an empty array + if (is_null()) + { + m_type = value_t::array; + m_value.array = create(); + assert_invariant(); + } + + // operator[] only works for arrays + if (JSON_LIKELY(is_array())) + { + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) + { + m_value.array->insert(m_value.array->end(), + idx - m_value.array->size() + 1, + basic_json()); + } + + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array; in that case, + using the [] operator with an index makes no sense. + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the `[]` operator.,operatorarray__size_type_const} + + @since version 1.0.0 + */ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (JSON_LIKELY(is_array())) + { + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference operator[](const typename object_t::key_type& key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (JSON_LIKELY(is_object())) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + reference operator[](T* key) + { + // implicitly convert null to object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // at only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + const_reference operator[](T* key) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.306 if the JSON value is not an object; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + template::value, int>::type = 0> + ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + + return default_value; + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /*! + @brief access specified object element via JSON Pointer with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(ptr); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const json_pointer&), this function does not throw + if the given key @a key was not found. + + @param[in] ptr a JSON pointer to the element to access + @param[in] default_value the value to return if @a ptr found no value + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.306 if the JSON value is not an objec; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value_ptr} + + @sa @ref operator[](const json_pointer&) for unchecked access by reference + + @since version 2.0.2 + */ + template::value, int>::type = 0> + ValueType value(const json_pointer& ptr, const ValueType& default_value) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + // if pointer resolves a value, return it or use default value + JSON_TRY + { + return ptr.get_checked(this); + } + JSON_CATCH (out_of_range&) + { + return default_value; + } + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const json_pointer&, ValueType) const + */ + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /*! + @brief access the first element + + Returns a reference to the first element in the container. For a JSON + container `c`, the expression `c.front()` is equivalent to `*c.begin()`. + + @return In case of a structured type (array or object), a reference to the + first element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on `null` value + + @liveexample{The following code shows an example for `front()`.,front} + + @sa @ref back() -- access the last element + + @since version 1.0.0 + */ + reference front() + { + return *begin(); + } + + /*! + @copydoc basic_json::front() + */ + const_reference front() const + { + return *cbegin(); + } + + /*! + @brief access the last element + + Returns a reference to the last element in the container. For a JSON + container `c`, the expression `c.back()` is equivalent to + @code {.cpp} + auto tmp = c.end(); + --tmp; + return *tmp; + @endcode + + @return In case of a structured type (array or object), a reference to the + last element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on a `null` value. See example + below. + + @liveexample{The following code shows an example for `back()`.,back} + + @sa @ref front() -- access the first element + + @since version 1.0.0 + */ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /*! + @copydoc basic_json::back() + */ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /*! + @brief remove element given an iterator + + Removes the element specified by iterator @a pos. The iterator @a pos must + be valid and dereferenceable. Thus the `end()` iterator (which is valid, + but is not dereferenceable) cannot be used as a value for @a pos. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] pos iterator to the element to remove + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.202 if called on an iterator which does not belong + to the current JSON value; example: `"iterator does not fit current + value"` + @throw invalid_iterator.205 if called on a primitive type with invalid + iterator (i.e., any iterator which is not `begin()`); example: `"iterator + out of range"` + + @complexity The complexity depends on the type: + - objects: amortized constant + - arrays: linear in distance between @a pos and the end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType} + + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType pos) + { + // make sure iterator fits the current value + if (JSON_UNLIKELY(this != pos.m_object)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_UNLIKELY(not pos.m_it.primitive_iterator.is_begin())) + { + JSON_THROW(invalid_iterator::create(205, "iterator out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove elements given an iterator range + + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] first iterator to the beginning of the range to remove + @param[in] last iterator past the end of the range to remove + @return Iterator following the last removed element. If the iterator @a + second refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.203 if called on iterators which does not belong + to the current JSON value; example: `"iterators do not fit current value"` + @throw invalid_iterator.204 if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` + + @complexity The complexity depends on the type: + - objects: `log(size()) + std::distance(first, last)` + - arrays: linear in the distance between @a first and @a last, plus linear + in the distance between @a last and end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType_IteratorType} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType first, IteratorType last) + { + // make sure iterator fits the current value + if (JSON_UNLIKELY(this != first.m_object or this != last.m_object)) + { + JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_LIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove element from a JSON object given a key + + Removes elements from a JSON object with the key value @a key. + + @param[in] key value of the elements to remove + + @return Number of elements removed. If @a ObjectType is the default + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). + + @post References and iterators to the erased elements are invalidated. + Other references and iterators are not affected. + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + + @complexity `log(size()) + count(key)` + + @liveexample{The example shows the effect of `erase()`.,erase__key_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + size_type erase(const typename object_t::key_type& key) + { + // this erase only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->erase(key); + } + + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + /*! + @brief remove element from a JSON array given an index + + Removes element from a JSON array at the index @a idx. + + @param[in] idx index of the element to remove + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + @throw out_of_range.401 when `idx >= size()`; example: `"array index 17 + is out of range"` + + @complexity Linear in distance between @a idx and the end of the container. + + @liveexample{The example shows the effect of `erase()`.,erase__size_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 + */ + void erase(const size_type idx) + { + // this erase only works for arrays + if (JSON_LIKELY(is_array())) + { + if (JSON_UNLIKELY(idx >= size())) + { + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else + { + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + } + + /// @} + + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /*! + @brief find an element in a JSON object + + Finds an element in a JSON object with key equivalent to @a key. If the + element is not found or the JSON value is not an object, end() is + returned. + + @note This method always returns @ref end() when executed on a JSON type + that is not an object. + + @param[in] key key value of the element to search for. + + @return Iterator to an element with key equivalent to @a key. If no such + element is found or the JSON value is not an object, past-the-end (see + @ref end()) iterator is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `find()` is used.,find__key_type} + + @since version 1.0.0 + */ + template + iterator find(KeyT&& key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief find an element in a JSON object + @copydoc find(KeyT&&) + */ + template + const_iterator find(KeyT&& key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief returns the number of occurrences of a key in a JSON object + + Returns the number of elements with key @a key. If ObjectType is the + default `std::map` type, the return value will always be `0` (@a key was + not found) or `1` (@a key was found). + + @note This method always returns `0` when executed on a JSON type that is + not an object. + + @param[in] key key value of the element to count + + @return Number of elements with key @a key. If the JSON value is not an + object, the return value will be `0`. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `count()` is used.,count} + + @since version 1.0.0 + */ + template + size_type count(KeyT&& key) const + { + // return 0 for all nonobject types + return is_object() ? m_value.object->count(std::forward(key)) : 0; + } + + /// @} + + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /*! + @brief returns an iterator to the first element + + Returns an iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `begin()`.,begin} + + @sa @ref cbegin() -- returns a const iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /*! + @copydoc basic_json::cbegin() + */ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /*! + @brief returns a const iterator to the first element + + Returns a const iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).begin()`. + + @liveexample{The following code shows an example for `cbegin()`.,cbegin} + + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /*! + @brief returns an iterator to one past the last element + + Returns an iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `end()`.,end} + + @sa @ref cend() -- returns a const iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /*! + @copydoc basic_json::cend() + */ + const_iterator end() const noexcept + { + return cend(); + } + + /*! + @brief returns a const iterator to one past the last element + + Returns a const iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).end()`. + + @liveexample{The following code shows an example for `cend()`.,cend} + + @sa @ref end() -- returns an iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /*! + @brief returns an iterator to the reverse-beginning + + Returns an iterator to the reverse-beginning; that is, the last element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(end())`. + + @liveexample{The following code shows an example for `rbegin()`.,rbegin} + + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /*! + @copydoc basic_json::crbegin() + */ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /*! + @brief returns an iterator to the reverse-end + + Returns an iterator to the reverse-end; that is, one before the first + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(begin())`. + + @liveexample{The following code shows an example for `rend()`.,rend} + + @sa @ref crend() -- returns a const reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /*! + @copydoc basic_json::crend() + */ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /*! + @brief returns a const reverse iterator to the last element + + Returns a const iterator to the reverse-beginning; that is, the last + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rbegin()`. + + @liveexample{The following code shows an example for `crbegin()`.,crbegin} + + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /*! + @brief returns a const reverse iterator to one before the first + + Returns a const reverse iterator to the reverse-end; that is, one before + the first element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rend()`. + + @liveexample{The following code shows an example for `crend()`.,crend} + + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without iterator_wrapper: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without iterator proxy: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with iterator proxy: + + @code{cpp} + for (auto it : json::iterator_wrapper(j_object)) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). + + @param[in] ref reference to a JSON value + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the wrapper is used,iterator_wrapper} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @note The name of this function is not yet final and may change in the + future. + + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use @ref items() instead; + that is, replace `json::iterator_wrapper(j)` with `j.items()`. + */ + JSON_DEPRECATED + static iteration_proxy iterator_wrapper(reference ref) noexcept + { + return ref.items(); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + JSON_DEPRECATED + static iteration_proxy iterator_wrapper(const_reference ref) noexcept + { + return ref.items(); + } + + /*! + @brief helper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without `items()` function: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without `items()` function: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with `items()` function: + + @code{cpp} + for (auto it : j_object.items()) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). For primitive types (e.g., numbers), + `key()` returns an empty string. + + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the function is used.,items} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 3.x.x. + */ + iteration_proxy items() noexcept + { + return iteration_proxy(*this); + } + + /*! + @copydoc items() + */ + iteration_proxy items() const noexcept + { + return iteration_proxy(*this); + } + + /// @} + + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /*! + @brief checks whether the container is empty. + + Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `true` + boolean | `false` + string | `false` + number | `false` + object | result of function `object_t::empty()` + array | result of function `array_t::empty()` + + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `begin() == end()`. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + bool empty() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_value.object->empty(); + } + + default: + { + // all other types are nonempty + return false; + } + } + } + + /*! + @brief returns the number of elements + + Returns the number of elements in a JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` + boolean | `1` + string | `1` + number | `1` + object | result of function object_t::size() + array | result of function array_t::size() + + @liveexample{The following code calls `size()` on the different value + types.,size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `std::distance(begin(), end())`. + + @sa @ref empty() -- checks whether the container is empty + @sa @ref max_size() -- returns the maximal number of elements + + @since version 1.0.0 + */ + size_type size() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_value.object->size(); + } + + default: + { + // all other types have size 1 + return 1; + } + } + } + + /*! + @brief returns the maximum possible number of elements + + Returns the maximum number of elements a JSON value is able to hold due to + system or library implementation limitations, i.e. `std::distance(begin(), + end())` for the JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` (same as `size()`) + boolean | `1` (same as `size()`) + string | `1` (same as `size()`) + number | `1` (same as `size()`) + object | result of function `object_t::max_size()` + array | result of function `array_t::max_size()` + + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of returning `b.size()` where `b` is the largest + possible JSON value. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + size_type max_size() const noexcept + { + switch (m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_value.object->max_size(); + } + + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /*! + @brief clears the contents + + Clears the content of a JSON value and resets it to the default value as + if @ref basic_json(value_t) would have been called with the current value + type from @ref type(): + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @post Has the same effect as calling + @code {.cpp} + *this = basic_json(type()); + @endcode + + @liveexample{The example below shows the effect of `clear()` to different + JSON types.,clear} + + @complexity Linear in the size of the JSON value. + + @iterators All iterators, pointers and references related to this container + are invalidated. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @sa @ref basic_json(value_t) -- constructor that creates an object with the + same value than calling `clear()` + + @since version 1.0.0 + */ + void clear() noexcept + { + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: + { + m_value.number_float = 0.0; + break; + } + + case value_t::boolean: + { + m_value.boolean = false; + break; + } + + case value_t::string: + { + m_value.string->clear(); + break; + } + + case value_t::array: + { + m_value.array->clear(); + break; + } + + case value_t::object: + { + m_value.object->clear(); + break; + } + + default: + break; + } + } + + /*! + @brief add an object to an array + + Appends the given element @a val to the end of the JSON value. If the + function is called on a JSON null value, an empty array is created before + appending @a val. + + @param[in] val the value to add to the JSON array + + @throw type_error.308 when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON array. Note how the `null` value was silently + converted to a JSON array.,push_back} + + @since version 1.0.0 + */ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (move semantics) + m_value.array->push_back(std::move(val)); + // invalidate object + val.m_type = value_t::null; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array + m_value.array->push_back(val); + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + Inserts the given element @a val to the JSON object. If the function is + called on a JSON null value, an empty object is created before inserting + @a val. + + @param[in] val the value to add to the JSON object + + @throw type_error.308 when called on a type other than JSON object or + null; example: `"cannot use push_back() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON object. Note how the `null` value was silently + converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 + */ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (JSON_UNLIKELY(not(is_null() or is_object()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array + m_value.object->insert(val); + } + + /*! + @brief add an object to an object + @copydoc push_back(const typename object_t::value_type&) + */ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param[in] init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(initializer_list_t init) + { + if (is_object() and init.size() == 2 and (*init.begin())->is_string()) + { + basic_json&& key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(initializer_list_t) + */ + reference operator+=(initializer_list_t init) + { + push_back(init); + return *this; + } + + /*! + @brief add an object to an array + + Creates a JSON value from the passed parameters @a args to the end of the + JSON value. If the function is called on a JSON null value, an empty array + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @throw type_error.311 when called on a type other than JSON array or + null; example: `"cannot use emplace_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` can be used to add + elements to a JSON array. Note how the `null` value was silently converted + to a JSON array.,emplace_back} + + @since version 2.0.8 + */ + template + void emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) + m_value.array->emplace_back(std::forward(args)...); + } + + /*! + @brief add an object to an object if key does not exist + + Inserts a new element into a JSON object constructed in-place with the + given @a args if there is no element with the key in the container. If the + function is called on a JSON null value, an empty object is created before + appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return a pair consisting of an iterator to the inserted element, or the + already-existing element if no insertion happened, and a bool + denoting whether the insertion took place. + + @throw type_error.311 when called on a type other than JSON object or + null; example: `"cannot use emplace() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `emplace()` can be used to add elements + to a JSON object. Note how the `null` value was silently converted to a + JSON object. Further note how no value is added if there was already one + value stored with the same key.,emplace} + + @since version 2.0.8 + */ + template + std::pair emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_object()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + auto res = m_value.object->emplace(std::forward(args)...); + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; + } + + /*! + @brief inserts element + + Inserts element @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] val element to insert + @return iterator pointing to the inserted @a val. + + @throw type_error.309 if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Constant plus linear in the distance between @a pos and end of + the container. + + @liveexample{The example shows how `insert()` is used.,insert} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const basic_json& val) + { + // insert only works for arrays + if (JSON_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts element + @copydoc insert(const_iterator, const basic_json&) + */ + iterator insert(const_iterator pos, basic_json&& val) + { + return insert(pos, val); + } + + /*! + @brief inserts elements + + Inserts @a cnt copies of @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert + @return iterator pointing to the first element inserted, or @a pos if + `cnt==0` + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Linear in @a cnt plus linear in the distance between @a pos + and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__count} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + // insert only works for arrays + if (JSON_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)` before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + @throw invalid_iterator.211 if @a first or @a last are iterators into + container for which insert is called; example: `"passed iterators may not + belong to container"` + + @return iterator pointing to the first element inserted, or @a pos if + `first==last` + + @complexity Linear in `std::distance(first, last)` plus linear in the + distance between @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__range} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + // insert only works for arrays + if (JSON_UNLIKELY(not is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + if (JSON_UNLIKELY(first.m_object == this)) + { + JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert( + pos.m_it.array_iterator, + first.m_it.array_iterator, + last.m_it.array_iterator); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from initializer list @a ilist before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] ilist initializer list to insert the values from + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @return iterator pointing to the first element inserted, or @a pos if + `ilist` is empty + + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__ilist} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, initializer_list_t ilist) + { + // insert only works for arrays + if (JSON_UNLIKELY(not is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end()); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)`. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than objects; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number + of elements to insert. + + @liveexample{The example shows how `insert()` is used.,insert__range_object} + + @since version 3.0.0 + */ + void insert(const_iterator first, const_iterator last) + { + // insert only works for objects + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_UNLIKELY(not first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from JSON object @a j and overwrites existing keys. + + @param[in] j JSON object to read values from + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_reference j) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()))); + } + + for (auto it = j.cbegin(); it != j.cend(); ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from from range `[first, last)` and overwrites existing + keys. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used__range.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_iterator first, const_iterator last) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_UNLIKELY(not first.m_object->is_object() + or not first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + for (auto it = first; it != last; ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); + assert_invariant(); + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw type_error.310 when JSON value is not an array; example: `"cannot + use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how arrays can be swapped with + `swap()`.,swap__array_t} + + @since version 1.0.0 + */ + void swap(array_t& other) + { + // swap only works for arrays + if (JSON_LIKELY(is_array())) + { + std::swap(*(m_value.array), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON object with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other object to exchange the contents with + + @throw type_error.310 when JSON value is not an object; example: + `"cannot use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how objects can be swapped with + `swap()`.,swap__object_t} + + @since version 1.0.0 + */ + void swap(object_t& other) + { + // swap only works for objects + if (JSON_LIKELY(is_object())) + { + std::swap(*(m_value.object), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other string to exchange the contents with + + @throw type_error.310 when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__string_t} + + @since version 1.0.0 + */ + void swap(string_t& other) + { + // swap only works for strings + if (JSON_LIKELY(is_string())) + { + std::swap(*(m_value.string), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /// @} + + public: + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + /*! + @brief comparison: equal + + Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) they are from the same type and (2) + their stored values are the same according to their respective + `operator==`. + - Integer and floating-point numbers are automatically converted before + comparison. Note than two NaN values are always treated as unequal. + - Two JSON null values are equal. + + @note Floating-point inside JSON values numbers are compared with + `json::number_float_t::operator==` which is `double::operator==` by + default. To compare floating-point while respecting an epsilon, an alternative + [comparison function](https://github.com/mariokonrad/marnav/blob/master/src/marnav/math/floatingpoint.hpp#L34-#L39) + could be used, for instance + @code {.cpp} + template::value, T>::type> + inline bool is_same(T a, T b, T epsilon = std::numeric_limits::epsilon()) noexcept + { + return std::abs(a - b) <= epsilon; + } + @endcode + + @note NaN values never compare equal to themselves or to other NaN values. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are equal + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__equal} + + @since version 1.0.0 + */ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + return (*lhs.m_value.array == *rhs.m_value.array); + + case value_t::object: + return (*lhs.m_value.object == *rhs.m_value.object); + + case value_t::null: + return true; + + case value_t::string: + return (*lhs.m_value.string == *rhs.m_value.string); + + case value_t::boolean: + return (lhs.m_value.boolean == rhs.m_value.boolean); + + case value_t::number_integer: + return (lhs.m_value.number_integer == rhs.m_value.number_integer); + + case value_t::number_unsigned: + return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned); + + case value_t::number_float: + return (lhs.m_value.number_float == rhs.m_value.number_float); + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return (static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float); + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return (lhs.m_value.number_float == static_cast(rhs.m_value.number_integer)); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return (static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float); + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return (lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned)); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return (static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return (lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned)); + } + + return false; + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs == basic_json(rhs)); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) == rhs); + } + + /*! + @brief comparison: not equal + + Compares two JSON values for inequality by calculating `not (lhs == rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are not equal + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__notequal} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs == rhs); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs != basic_json(rhs)); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) != rhs); + } + + /*! + @brief comparison: less than + + Compares whether one JSON value @a lhs is less than another JSON value @a + rhs according to the following rules: + - If @a lhs and @a rhs have the same type, the values are compared using + the default `<` operator. + - Integer and floating-point numbers are automatically converted before + comparison + - In case @a lhs and @a rhs have different types, the values are ignored + and the order of the types is considered, see + @ref operator<(const value_t, const value_t). + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__less} + + @since version 1.0.0 + */ + friend bool operator<(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + return (*lhs.m_value.array) < (*rhs.m_value.array); + + case value_t::object: + return *lhs.m_value.object < *rhs.m_value.object; + + case value_t::null: + return false; + + case value_t::string: + return *lhs.m_value.string < *rhs.m_value.string; + + case value_t::boolean: + return lhs.m_value.boolean < rhs.m_value.boolean; + + case value_t::number_integer: + return lhs.m_value.number_integer < rhs.m_value.number_integer; + + case value_t::number_unsigned: + return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; + + case value_t::number_float: + return lhs.m_value.number_float < rhs.m_value.number_float; + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs < basic_json(rhs)); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) < rhs); + } + + /*! + @brief comparison: less than or equal + + Compares whether one JSON value @a lhs is less than or equal to another + JSON value by calculating `not (rhs < lhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greater} + + @since version 1.0.0 + */ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + return not (rhs < lhs); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs <= basic_json(rhs)); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) <= rhs); + } + + /*! + @brief comparison: greater than + + Compares whether one JSON value @a lhs is greater than another + JSON value by calculating `not (lhs <= rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__lessequal} + + @since version 1.0.0 + */ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs <= rhs); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs > basic_json(rhs)); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) > rhs); + } + + /*! + @brief comparison: greater than or equal + + Compares whether one JSON value @a lhs is greater than or equal to another + JSON value by calculating `not (lhs < rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greaterequal} + + @since version 1.0.0 + */ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs < rhs); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs >= basic_json(rhs)); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) >= rhs); + } + + /// @} + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ + + /*! + @brief serialize to stream + + Serialize the given JSON value @a j to the output stream @a o. The JSON + value will be serialized using the @ref dump member function. + + - The indentation of the output can be controlled with the member variable + `width` of the output stream @a o. For instance, using the manipulator + `std::setw(4)` on @a o sets the indentation level to `4` and the + serialization result is the same as calling `dump(4)`. + + - The indentation character can be controlled with the member variable + `fill` of the output stream @a o. For instance, the manipulator + `std::setfill('\\t')` sets indentation to use a tab character rather than + the default space character. + + @param[in,out] o stream to serialize to + @param[in] j JSON value to serialize + + @return the stream @a o + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + + @complexity Linear. + + @liveexample{The example below shows the serialization with different + parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0; indentation character added in version 3.0.0 + */ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as indentation parameter if nonzero + const bool pretty_print = (o.width() > 0); + const auto indentation = (pretty_print ? o.width() : 0); + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // do the actual serialization + serializer s(detail::output_adapter(o), o.fill()); + s.dump(j, pretty_print, false, static_cast(indentation)); + return o; + } + + /*! + @brief serialize to stream + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use + @ref operator<<(std::ostream&, const basic_json&) + instead; that is, replace calls like `j >> o;` with `o << j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_DEPRECATED + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } + + /// @} + + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /*! + @brief deserialize from a compatible input + + This function reads from a compatible input. Examples are: + - an array of 1-byte values + - strings with character/literal type with size of 1 byte + - input streams + - container with contiguous storage of 1-byte values. Compatible container + types include `std::vector`, `std::string`, `std::array`, + `std::valarray`, and `std::initializer_list`. Furthermore, C-style + arrays can be used with `std::begin()`/`std::end()`. User-defined + containers can be used as long as they implement random-access iterators + and a contiguous storage. + + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @pre The container storage is contiguous. Violating this precondition + yields undefined behavior. **This precondition is enforced with an + assertion.** + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with a noncompliant container and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @param[in] i input to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an array.,parse__array__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function reading + from a contiguous container.,parse__contiguouscontainer__parser_callback_t} + + @since version 2.0.3 (contiguous containers) + */ + static basic_json parse(detail::input_adapter i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } + + /*! + @copydoc basic_json parse(detail::input_adapter, const parser_callback_t) + */ + static basic_json parse(detail::input_adapter& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } + + static bool accept(detail::input_adapter i) + { + return parser(i).accept(true); + } + + static bool accept(detail::input_adapter& i) + { + return parser(i).accept(true); + } + + /*! + @brief deserialize from an iterator range with contiguous storage + + This function reads from an iterator range of a container with contiguous + storage of 1-byte values. Compatible container types include + `std::vector`, `std::string`, `std::array`, `std::valarray`, and + `std::initializer_list`. Furthermore, C-style arrays can be used with + `std::begin()`/`std::end()`. User-defined containers can be used as long + as they implement random-access iterators and a contiguous storage. + + @pre The iterator range is contiguous. Violating this precondition yields + undefined behavior. **This precondition is enforced with an assertion.** + @pre Each element in the range has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with noncompliant iterators and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @tparam IteratorType iterator of container with contiguous storage + @param[in] first begin of the range to parse (included) + @param[in] last end of the range to parse (excluded) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return result of the deserialization + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an iterator range.,parse__iteratortype__parser_callback_t} + + @since version 2.0.3 + */ + template::iterator_category>::value, int>::type = 0> + static basic_json parse(IteratorType first, IteratorType last, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result); + return result; + } + + template::iterator_category>::value, int>::type = 0> + static bool accept(IteratorType first, IteratorType last) + { + return parser(detail::input_adapter(first, last)).accept(true); + } + + /*! + @brief deserialize from stream + @deprecated This stream operator is deprecated and will be removed in + version 4.0.0 of the library. Please use + @ref operator>>(std::istream&, basic_json&) + instead; that is, replace calls like `j << i;` with `i >> j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_DEPRECATED + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + return operator>>(i, j); + } + + /*! + @brief deserialize from stream + + Deserializes an input stream to a JSON value. + + @param[in,out] i input stream to read a serialized JSON value from + @param[in,out] j JSON value to write the deserialized input to + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below shows how a JSON value is constructed by + reading a serialization from a stream.,operator_deserialize} + + @sa parse(std::istream&, const parser_callback_t) for a variant with a + parser callback function to filter values while parsing + + @since version 1.0.0 + */ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + parser(detail::input_adapter(i)).parse(false, j); + return i; + } + + /// @} + + /////////////////////////// + // convenience functions // + /////////////////////////// + + /*! + @brief return the type as string + + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. + + @return a string representation of a the @a m_type member: + Value type | return value + ----------- | ------------- + null | `"null"` + boolean | `"boolean"` + string | `"string"` + number | `"number"` (for all number types) + object | `"object"` + array | `"array"` + discarded | `"discarded"` + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Constant. + + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,type_name} + + @sa @ref type() -- return the type of the JSON value + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + + @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` + since 3.0.0 + */ + const char* type_name() const noexcept + { + { + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + } + + + private: + ////////////////////// + // member variables // + ////////////////////// + + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + public: + /*! + @brief create a CBOR serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the CBOR (Concise + Binary Object Representation) serialization format. CBOR is a binary + serialization format which aims to be more compact than JSON itself, yet + more efficient to parse. + + The library uses the following mapping from JSON values types to + CBOR types according to the CBOR specification (RFC 7049): + + JSON value type | value/range | CBOR type | first byte + --------------- | ------------------------------------------ | ---------------------------------- | --------------- + null | `null` | Null | 0xF6 + boolean | `true` | True | 0xF5 + boolean | `false` | False | 0xF4 + number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B + number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A + number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 + number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 + number_integer | -24..-1 | Negative integer | 0x20..0x37 + number_integer | 0..23 | Integer | 0x00..0x17 + number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_unsigned | 0..23 | Integer | 0x00..0x17 + number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_float | *any value* | Double-Precision Float | 0xFB + string | *length*: 0..23 | UTF-8 string | 0x60..0x77 + string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 + string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 + string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A + string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B + array | *size*: 0..23 | array | 0x80..0x97 + array | *size*: 23..255 | array (1 byte follow) | 0x98 + array | *size*: 256..65535 | array (2 bytes follow) | 0x99 + array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A + array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B + object | *size*: 0..23 | map | 0xA0..0xB7 + object | *size*: 23..255 | map (1 byte follow) | 0xB8 + object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 + object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA + object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a CBOR value. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The following CBOR types are not used in the conversion: + - byte strings (0x40..0x5F) + - UTF-8 strings terminated by "break" (0x7F) + - arrays terminated by "break" (0x9F) + - maps terminated by "break" (0xBF) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - tagged items (0xC6..0xD4, 0xD8..0xDB) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + - half and single-precision floats (0xF9-0xFA) + - break (0xFF) + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in CBOR format.,to_cbor} + + @sa http://cbor.io + @sa @ref from_cbor(detail::input_adapter, const bool strict) for the + analogous deserialization + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9 + */ + static std::vector to_cbor(const basic_json& j) + { + std::vector result; + to_cbor(j, result); + return result; + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the MessagePack + serialization format. MessagePack is a binary serialization format which + aims to be more compact than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + MessagePack types according to the MessagePack specification: + + JSON value type | value/range | MessagePack type | first byte + --------------- | --------------------------------- | ---------------- | ---------- + null | `null` | nil | 0xC0 + boolean | `true` | true | 0xC3 + boolean | `false` | false | 0xC2 + number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3 + number_integer | -2147483648..-32769 | int32 | 0xD2 + number_integer | -32768..-129 | int16 | 0xD1 + number_integer | -128..-33 | int8 | 0xD0 + number_integer | -32..-1 | negative fixint | 0xE0..0xFF + number_integer | 0..127 | positive fixint | 0x00..0x7F + number_integer | 128..255 | uint 8 | 0xCC + number_integer | 256..65535 | uint 16 | 0xCD + number_integer | 65536..4294967295 | uint 32 | 0xCE + number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_unsigned | 0..127 | positive fixint | 0x00..0x7F + number_unsigned | 128..255 | uint 8 | 0xCC + number_unsigned | 256..65535 | uint 16 | 0xCD + number_unsigned | 65536..4294967295 | uint 32 | 0xCE + number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_float | *any value* | float 64 | 0xCB + string | *length*: 0..31 | fixstr | 0xA0..0xBF + string | *length*: 32..255 | str 8 | 0xD9 + string | *length*: 256..65535 | str 16 | 0xDA + string | *length*: 65536..4294967295 | str 32 | 0xDB + array | *size*: 0..15 | fixarray | 0x90..0x9F + array | *size*: 16..65535 | array 16 | 0xDC + array | *size*: 65536..4294967295 | array 32 | 0xDD + object | *size*: 0..15 | fix map | 0x80..0x8F + object | *size*: 16..65535 | map 16 | 0xDE + object | *size*: 65536..4294967295 | map 32 | 0xDF + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a MessagePack value. + + @note The following values can **not** be converted to a MessagePack value: + - strings with more than 4294967295 bytes + - arrays with more than 4294967295 elements + - objects with more than 4294967295 elements + + @note The following MessagePack types are not used in the conversion: + - bin 8 - bin 32 (0xC4..0xC6) + - ext 8 - ext 32 (0xC7..0xC9) + - float 32 (0xCA) + - fixext 1 - fixext 16 (0xD4..0xD8) + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in MessagePack format.,to_msgpack} + + @sa http://msgpack.org + @sa @ref from_msgpack(const std::vector&, const size_t) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9 + */ + static std::vector to_msgpack(const basic_json& j) + { + std::vector result; + to_msgpack(j, result); + return result; + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + /*! + @brief create a UBJSON serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the UBJSON + (Universal Binary JSON) serialization format. UBJSON aims to be more compact + than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + UBJSON types according to the UBJSON specification: + + JSON value type | value/range | UBJSON type | marker + --------------- | --------------------------------- | ----------- | ------ + null | `null` | null | `Z` + boolean | `true` | true | `T` + boolean | `false` | false | `F` + number_integer | -9223372036854775808..-2147483649 | int64 | `L` + number_integer | -2147483648..-32769 | int32 | `l` + number_integer | -32768..-129 | int16 | `I` + number_integer | -128..127 | int8 | `i` + number_integer | 128..255 | uint8 | `U` + number_integer | 256..32767 | int16 | `I` + number_integer | 32768..2147483647 | int32 | `l` + number_integer | 2147483648..9223372036854775807 | int64 | `L` + number_unsigned | 0..127 | int8 | `i` + number_unsigned | 128..255 | uint8 | `U` + number_unsigned | 256..32767 | int16 | `I` + number_unsigned | 32768..2147483647 | int32 | `l` + number_unsigned | 2147483648..9223372036854775807 | int64 | `L` + number_float | *any value* | float64 | `D` + string | *with shortest length indicator* | string | `S` + array | *see notes on optimized format* | array | `[` + object | *see notes on optimized format* | map | `{` + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a UBJSON value. + + @note The following values can **not** be converted to a UBJSON value: + - strings with more than 9223372036854775807 bytes (theoretical) + - unsigned integer numbers above 9223372036854775807 + + @note The following markers are not used in the conversion: + - `Z`: no-op values are not created. + - `C`: single-byte strings are serialized with `S` markers. + + @note Any UBJSON output created @ref to_ubjson can be successfully parsed + by @ref from_ubjson. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The optimized formats for containers are supported: Parameter + @a use_size adds size information to the beginning of a container and + removes the closing marker. Parameter @a use_type further checks + whether all elements of a container have the same type and adds the + type marker to the beginning of the container. The @a use_type + parameter must only be used together with @a use_size = true. Note + that @a use_size = true alone may result in larger representations - + the benefit of this parameter is that the receiving side is + immediately informed on the number of elements of the container. + + @param[in] j JSON value to serialize + @param[in] use_size whether to add size annotations to container types + @param[in] use_type whether to add type annotations to container types + (must be combined with @a use_size = true) + @return UBJSON serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in UBJSON format.,to_ubjson} + + @sa http://ubjson.org + @sa @ref from_ubjson(detail::input_adapter, const bool strict) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + + @since version 3.1.0 + */ + static std::vector to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector result; + to_ubjson(j, result, use_size, use_type); + return result; + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + /*! + @brief create a JSON value from an input in CBOR format + + Deserializes a given input @a i to a JSON value using the CBOR (Concise + Binary Object Representation) serialization format. + + The library maps CBOR types to JSON value types as follows: + + CBOR type | JSON value type | first byte + ---------------------- | --------------- | ---------- + Integer | number_unsigned | 0x00..0x17 + Unsigned integer | number_unsigned | 0x18 + Unsigned integer | number_unsigned | 0x19 + Unsigned integer | number_unsigned | 0x1A + Unsigned integer | number_unsigned | 0x1B + Negative integer | number_integer | 0x20..0x37 + Negative integer | number_integer | 0x38 + Negative integer | number_integer | 0x39 + Negative integer | number_integer | 0x3A + Negative integer | number_integer | 0x3B + Negative integer | number_integer | 0x40..0x57 + UTF-8 string | string | 0x60..0x77 + UTF-8 string | string | 0x78 + UTF-8 string | string | 0x79 + UTF-8 string | string | 0x7A + UTF-8 string | string | 0x7B + UTF-8 string | string | 0x7F + array | array | 0x80..0x97 + array | array | 0x98 + array | array | 0x99 + array | array | 0x9A + array | array | 0x9B + array | array | 0x9F + map | object | 0xA0..0xB7 + map | object | 0xB8 + map | object | 0xB9 + map | object | 0xBA + map | object | 0xBB + map | object | 0xBF + False | `false` | 0xF4 + True | `true` | 0xF5 + Nill | `null` | 0xF6 + Half-Precision Float | number_float | 0xF9 + Single-Precision Float | number_float | 0xFA + Double-Precision Float | number_float | 0xFB + + @warning The mapping is **incomplete** in the sense that not all CBOR + types can be converted to a JSON value. The following CBOR types + are not supported and will yield parse errors (parse_error.112): + - byte strings (0x40..0x5F) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - tagged items (0xC6..0xD4, 0xD8..0xDB) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + + @warning CBOR allows map keys of any type, whereas JSON only allows + strings as keys in object values. Therefore, CBOR maps with keys + other than UTF-8 strings are rejected (parse_error.113). + + @note Any CBOR output created @ref to_cbor can be successfully parsed by + @ref from_cbor. + + @param[in] i an input in CBOR format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @return deserialized JSON value + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from CBOR were + used in the given input @a v or if the input is not valid CBOR + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} + + @sa http://cbor.io + @sa @ref to_cbor(const basic_json&) for the analogous serialization + @sa @ref from_msgpack(detail::input_adapter, const bool) for the + related MessagePack format + @sa @ref from_ubjson(detail::input_adapter, const bool) for the related + UBJSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 + */ + static basic_json from_cbor(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_cbor(strict); + } + + /*! + @copydoc from_cbor(detail::input_adapter, const bool) + */ + template::value, int> = 0> + static basic_json from_cbor(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_cbor(strict); + } + + /*! + @brief create a JSON value from an input in MessagePack format + + Deserializes a given input @a i to a JSON value using the MessagePack + serialization format. + + The library maps MessagePack types to JSON value types as follows: + + MessagePack type | JSON value type | first byte + ---------------- | --------------- | ---------- + positive fixint | number_unsigned | 0x00..0x7F + fixmap | object | 0x80..0x8F + fixarray | array | 0x90..0x9F + fixstr | string | 0xA0..0xBF + nil | `null` | 0xC0 + false | `false` | 0xC2 + true | `true` | 0xC3 + float 32 | number_float | 0xCA + float 64 | number_float | 0xCB + uint 8 | number_unsigned | 0xCC + uint 16 | number_unsigned | 0xCD + uint 32 | number_unsigned | 0xCE + uint 64 | number_unsigned | 0xCF + int 8 | number_integer | 0xD0 + int 16 | number_integer | 0xD1 + int 32 | number_integer | 0xD2 + int 64 | number_integer | 0xD3 + str 8 | string | 0xD9 + str 16 | string | 0xDA + str 32 | string | 0xDB + array 16 | array | 0xDC + array 32 | array | 0xDD + map 16 | object | 0xDE + map 32 | object | 0xDF + negative fixint | number_integer | 0xE0-0xFF + + @warning The mapping is **incomplete** in the sense that not all + MessagePack types can be converted to a JSON value. The following + MessagePack types are not supported and will yield parse errors: + - bin 8 - bin 32 (0xC4..0xC6) + - ext 8 - ext 32 (0xC7..0xC9) + - fixext 1 - fixext 16 (0xD4..0xD8) + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @param[in] i an input in MessagePack format convertible to an input + adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from MessagePack were + used in the given input @a i or if the input is not valid MessagePack + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + MessagePack format to a JSON value.,from_msgpack} + + @sa http://msgpack.org + @sa @ref to_msgpack(const basic_json&) for the analogous serialization + @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR + format + @sa @ref from_ubjson(detail::input_adapter, const bool) for the related + UBJSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 + */ + static basic_json from_msgpack(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_msgpack(strict); + } + + /*! + @copydoc from_msgpack(detail::input_adapter, const bool) + */ + template::value, int> = 0> + static basic_json from_msgpack(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_msgpack(strict); + } + + /*! + @brief create a JSON value from an input in UBJSON format + + Deserializes a given input @a i to a JSON value using the UBJSON (Universal + Binary JSON) serialization format. + + The library maps UBJSON types to JSON value types as follows: + + UBJSON type | JSON value type | marker + ----------- | --------------------------------------- | ------ + no-op | *no value, next value is read* | `N` + null | `null` | `Z` + false | `false` | `F` + true | `true` | `T` + float32 | number_float | `d` + float64 | number_float | `D` + uint8 | number_unsigned | `U` + int8 | number_integer | `i` + int16 | number_integer | `I` + int32 | number_integer | `l` + int64 | number_integer | `L` + string | string | `S` + char | string | `C` + array | array (optimized values are supported) | `[` + object | object (optimized values are supported) | `{` + + @note The mapping is **complete** in the sense that any UBJSON value can + be converted to a JSON value. + + @param[in] i an input in UBJSON format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if a parse error occurs + @throw parse_error.113 if a string could not be parsed successfully + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + UBJSON format to a JSON value.,from_ubjson} + + @sa http://ubjson.org + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + analogous serialization + @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR + format + @sa @ref from_msgpack(detail::input_adapter, const bool) for the related + MessagePack format + + @since version 3.1.0 + */ + static basic_json from_ubjson(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_ubjson(strict); + } + + template::value, int> = 0> + static basic_json from_ubjson(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_ubjson(strict); + } + + /// @} + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer} + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitive values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this function, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw parse_error.104 if the JSON patch does not consist of an array of + objects + + @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @throw out_of_range.401 if an array index is out of range. + + @throw out_of_range.403 if a JSON pointer inside the patch could not be + resolved successfully in the current JSON value; example: `"key baz not + found"` + + @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", + "move") + + @throw other_error.501 if "test" operation was unsuccessful + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string & op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.is_root()) + { + result = val; + } + else + { + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = json_pointer::array_index(last_path); + if (JSON_UNLIKELY(static_cast(idx) > parent.size())) + { + // avoid undefined behavior + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + else + { + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + } + break; + } + + default: + { + // if there exists a parent it cannot be primitive + assert(false); // LCOV_EXCL_LINE + } + } + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (JSON_LIKELY(it != parent.end())) + { + parent.erase(it); + } + else + { + JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found")); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(static_cast(json_pointer::array_index(last_path))); + } + }; + + // type check: top level value must be an array + if (JSON_UNLIKELY(not json_patch.is_array())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // iterate and apply the operations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json & + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (JSON_UNLIKELY(it == val.m_value.object->end())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'")); + } + + // check if result is of type string + if (JSON_UNLIKELY(string_type and not it->second.is_string())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'")); + } + + // no error: return value + return it->second; + }; + + // type check: every element of the array must be an object + if (JSON_UNLIKELY(not val.is_object())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // collect mandatory members + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const std::string from_path = get_value("copy", "from", true); + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The copy is functionally identical to an "add" + // operation at the target location using the value + // specified in the "from" member. + operation_add(ptr, v); + break; + } + + case patch_operations::test: + { + bool success = false; + JSON_TRY + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_CATCH (out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (JSON_UNLIKELY(not success)) + { + JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump())); + } + + break; + } + + case patch_operations::invalid: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid")); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to compare from + @param[in] target JSON value to compare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + @sa @ref merge_patch -- apply a JSON Merge Patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + static basic_json diff(const basic_json& source, const basic_json& target, + const std::string& path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + } + else + { + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + std::size_t i = 0; + while (i < source.size() and i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + std::to_string(i)}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.cbegin(); it != source.cend(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.cbegin(); it != target.cend(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + break; + } + } + } + + return result; + } + + /// @} + + //////////////////////////////// + // JSON Merge Patch functions // + //////////////////////////////// + + /// @name JSON Merge Patch functions + /// @{ + + /*! + @brief applies a JSON Merge Patch + + The merge patch format is primarily intended for use with the HTTP PATCH + method as a means of describing a set of modifications to a target + resource's content. This function applies a merge patch to the current + JSON value. + + The function implements the following algorithm from Section 2 of + [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396): + + ``` + define MergePatch(Target, Patch): + if Patch is an Object: + if Target is not an Object: + Target = {} // Ignore the contents and set it to an empty Object + for each Name/Value pair in Patch: + if Value is null: + if Name exists in Target: + remove the Name/Value pair from Target + else: + Target[Name] = MergePatch(Target[Name], Value) + return Target + else: + return Patch + ``` + + Thereby, `Target` is the current object; that is, the patch is applied to + the current value. + + @param[in] patch the patch to apply + + @complexity Linear in the lengths of @a patch. + + @liveexample{The following code shows how a JSON Merge Patch is applied to + a JSON document.,merge_patch} + + @sa @ref patch -- apply a JSON patch + @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) + + @since version 3.0.0 + */ + void merge_patch(const basic_json& patch) + { + if (patch.is_object()) + { + if (not is_object()) + { + *this = object(); + } + for (auto it = patch.begin(); it != patch.end(); ++it) + { + if (it.value().is_null()) + { + erase(it.key()); + } + else + { + operator[](it.key()).merge_patch(it.value()); + } + } + } + else + { + *this = patch; + } + } + + /// @} +}; +} // namespace nlohmann + +/////////////////////// +// nonmember support // +/////////////////////// + +// specialization of std::swap, and std::hash +namespace std +{ +/*! +@brief exchanges the values of two JSON objects + +@since version 1.0.0 +*/ +template<> +inline void swap(nlohmann::json& j1, + nlohmann::json& j2) noexcept( + is_nothrow_move_constructible::value and + is_nothrow_move_assignable::value + ) +{ + j1.swap(j2); +} + +/// hash value for JSON objects +template<> +struct hash +{ + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ + std::size_t operator()(const nlohmann::json& j) const + { + // a naive hashing via the string representation + const auto& h = hash(); + return h(j.dump()); + } +}; + +/// specialization for std::less +/// @note: do not remove the space after '<', +/// see https://github.com/nlohmann/json/pull/679 +template<> +struct less< ::nlohmann::detail::value_t> +{ + /*! + @brief compare two value_t enum values + @since version 3.0.0 + */ + bool operator()(nlohmann::detail::value_t lhs, + nlohmann::detail::value_t rhs) const noexcept + { + return nlohmann::detail::operator<(lhs, rhs); + } +}; + +} // namespace std + +/*! +@brief user-defined string literal for JSON values + +This operator implements a user-defined string literal for JSON objects. It +can be used by adding `"_json"` to a string literal and returns a JSON object +if no parse error occurred. + +@param[in] s a string representation of a JSON object +@param[in] n the length of string @a s +@return a JSON object + +@since version 1.0.0 +*/ +inline nlohmann::json operator "" _json(const char* s, std::size_t n) +{ + return nlohmann::json::parse(s, s + n); +} + +/*! +@brief user-defined string literal for JSON pointer + +This operator implements a user-defined string literal for JSON Pointers. It +can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer +object if no parse error occurred. + +@param[in] s a string representation of a JSON Pointer +@param[in] n the length of string @a s +@return a JSON pointer object + +@since version 2.0.0 +*/ +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) +{ + return nlohmann::json::json_pointer(std::string(s, n)); +} + +// #include + + +// restore GCC/clang diagnostic settings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic pop +#endif +#if defined(__clang__) + #pragma GCC diagnostic pop +#endif + +// clean up +#undef JSON_CATCH +#undef JSON_THROW +#undef JSON_TRY +#undef JSON_LIKELY +#undef JSON_UNLIKELY +#undef JSON_DEPRECATED +#undef JSON_HAS_CPP_14 +#undef JSON_HAS_CPP_17 +#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION +#undef NLOHMANN_BASIC_JSON_TPL +#undef NLOHMANN_JSON_HAS_HELPER + + +#endif diff --git a/paperio/dockers/cpp17/Dockerfile b/paperio/dockers/cpp17/Dockerfile new file mode 100644 index 0000000..1af26f7 --- /dev/null +++ b/paperio/dockers/cpp17/Dockerfile @@ -0,0 +1,18 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Boris Kolganov + +RUN \ + apt-get update -y && \ + apt-get install -y software-properties-common && \ + add-apt-repository ppa:ubuntu-toolchain-r/test -y && \ + apt-get update -y && \ + apt-get install -y g++-7 make cmake + +COPY Makefile ./ +COPY ./nlohmann ./nlohmann + +ENV SOLUTION_CODE_ENTRYPOINT=main.cpp +ENV COMPILED_FILE_PATH=/opt/client/a.out +ENV SOLUTION_CODE_PATH=/opt/client/solution/ +ENV COMPILATION_COMMAND='if [ -f $SOLUTION_CODE_PATH/__build__.sh ]; then cd $SOLUTION_CODE_PATH; . $SOLUTION_CODE_PATH/__build__.sh; else make; fi 2>&1 > /dev/null' +ENV RUN_COMMAND='/lib64/ld-linux-x86-64.so.2 $MOUNT_POINT' diff --git a/paperio/dockers/cpp17/Makefile b/paperio/dockers/cpp17/Makefile new file mode 100644 index 0000000..7ccffdb --- /dev/null +++ b/paperio/dockers/cpp17/Makefile @@ -0,0 +1,7 @@ +CXXFLAGS=-std=c++17 -O3 -m64 -pipe -w -pthread +CXX=g++-7 + +SRCS = $(shell find ${SOLUTION_CODE_PATH} -type f -name '*.cpp') + +all: ${SRCS} + ${CXX} ${CXXFLAGS} -o ${COMPILED_FILE_PATH} ${SRCS} diff --git a/paperio/dockers/cpp17/nlohmann/LICENSE.MIT b/paperio/dockers/cpp17/nlohmann/LICENSE.MIT new file mode 100644 index 0000000..8b0f700 --- /dev/null +++ b/paperio/dockers/cpp17/nlohmann/LICENSE.MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-2018 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. diff --git a/paperio/dockers/cpp17/nlohmann/json.hpp b/paperio/dockers/cpp17/nlohmann/json.hpp new file mode 100644 index 0000000..3dcb834 --- /dev/null +++ b/paperio/dockers/cpp17/nlohmann/json.hpp @@ -0,0 +1,17190 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 3.1.1 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +Copyright (c) 2013-2018 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. +*/ + +#ifndef NLOHMANN_JSON_HPP +#define NLOHMANN_JSON_HPP + +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 1 +#define NLOHMANN_JSON_VERSION_PATCH 1 + +#include // all_of, find, for_each +#include // assert +#include // and, not, or +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#include // istream, ostream +#include // iterator_traits, random_access_iterator_tag +#include // accumulate +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap + +// #include +#ifndef NLOHMANN_JSON_FWD_HPP +#define NLOHMANN_JSON_FWD_HPP + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string +#include // vector + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} + +#endif + +// #include + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// exclude unsupported compilers +#if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow for portable deprecation warnings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define JSON_DEPRECATED __declspec(deprecated) +#else + #define JSON_DEPRECATED +#endif + +// allow to disable exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) +#else + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER +#endif + +// manual branch prediction +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) + #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else + #define JSON_LIKELY(x) x + #define JSON_UNLIKELY(x) x +#endif + +// C++ language standard detection +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +/*! +@brief Helper to determine whether there's a key_type for T. + +This helper is used to tell associative containers apart from other containers +such as sequence containers. For instance, `std::map` passes the test as it +contains a `mapped_type`, whereas `std::vector` fails the test. + +@sa http://stackoverflow.com/a/7728728/266378 +@since version 1.0.0, overworked in version 2.0.6 +*/ +#define NLOHMANN_JSON_HAS_HELPER(type) \ + template struct has_##type { \ + private: \ + template \ + static int detect(U &&); \ + static void detect(...); \ + public: \ + static constexpr bool value = \ + std::is_integral()))>::value; \ + } + +// #include + + +#include // not +#include // size_t +#include // numeric_limits +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // declval + +// #include + +// #include + + +namespace nlohmann +{ +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ +///////////// +// helpers // +///////////// + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +/* +Implementation of two C++17 constructs: conjunction, negation. This is needed +to avoid evaluating all the traits in a condition + +For example: not std::is_same::value and has_value_type::value +will not compile when T = void (on MSVC at least). Whereas +conjunction>, has_value_type>::value will +stop evaluating if negation<...>::value == false + +Please note that those constructs must be used with caution, since symbols can +become very long quickly (which can slow down compilation and cause MSVC +internal compiler errors). Only use it when you have to (see example ahead). +*/ +template struct conjunction : std::true_type {}; +template struct conjunction : B1 {}; +template +struct conjunction : std::conditional, B1>::type {}; + +template struct negation : std::integral_constant {}; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +//////////////////////// +// has_/is_ functions // +//////////////////////// + +// source: https://stackoverflow.com/a/37193089/4116453 + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +NLOHMANN_JSON_HAS_HELPER(mapped_type); +NLOHMANN_JSON_HAS_HELPER(key_type); +NLOHMANN_JSON_HAS_HELPER(value_type); +NLOHMANN_JSON_HAS_HELPER(iterator); + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl +{ + static constexpr auto value = + std::is_constructible::value and + std::is_constructible::value; +}; + +template +struct is_compatible_object_type +{ + static auto constexpr value = is_compatible_object_type_impl < + conjunction>, + has_mapped_type, + has_key_type>::value, + typename BasicJsonType::object_t, CompatibleObjectType >::value; +}; + +template +struct is_basic_json_nested_type +{ + static auto constexpr value = std::is_same::value or + std::is_same::value or + std::is_same::value or + std::is_same::value; +}; + +template +struct is_compatible_array_type +{ + static auto constexpr value = + conjunction>, + negation>, + negation>, + negation>, + has_value_type, + has_iterator>::value; +}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + std::is_constructible::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type +{ + static constexpr auto value = + is_compatible_integer_type_impl < + std::is_integral::value and + not std::is_same::value, + RealIntegerType, CompatibleNumberIntegerType > ::value; +}; + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json +{ + private: + // also check the return type of from_json + template::from_json( + std::declval(), std::declval()))>::value>> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json +{ + private: + template < + typename U, + typename = enable_if_t::from_json(std::declval()))>::value >> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +template +struct has_to_json +{ + private: + template::to_json( + std::declval(), std::declval()))> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +template +struct is_compatible_complete_type +{ + static constexpr bool value = + not std::is_base_of::value and + not std::is_same::value and + not is_basic_json_nested_type::value and + has_to_json::value; +}; + +template +struct is_compatible_type + : conjunction, + is_compatible_complete_type> +{ +}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} +} + +// #include + + +#include // exception +#include // runtime_error +#include // to_string + +namespace nlohmann +{ +namespace detail +{ +//////////////// +// exceptions // +//////////////// + +/*! +@brief general exception of the @ref basic_json class + +This class is an extension of `std::exception` objects with a member @a id for +exception ids. It is used as the base class for all exceptions thrown by the +@ref basic_json class. This class can hence be used as "wildcard" to catch +exceptions. + +Subclasses: +- @ref parse_error for exceptions indicating a parse error +- @ref invalid_iterator for exceptions indicating errors with iterators +- @ref type_error for exceptions indicating executing a member function with + a wrong type +- @ref out_of_range for exceptions indicating access out of the defined range +- @ref other_error for exceptions indicating other library errors + +@internal +@note To have nothrow-copy-constructible exceptions, we internally use + `std::runtime_error` which can cope with arbitrary-length error messages. + Intermediate strings are built with static functions and then passed to + the actual constructor. +@endinternal + +@liveexample{The following code shows how arbitrary library exceptions can be +caught.,exception} + +@since version 3.0.0 +*/ +class exception : public std::exception +{ + public: + /// returns the explanatory string + const char* what() const noexcept override + { + return m.what(); + } + + /// the id of the exception + const int id; + + protected: + exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} + + static std::string name(const std::string& ename, int id_) + { + return "[json.exception." + ename + "." + std::to_string(id_) + "] "; + } + + private: + /// an exception object as storage for error messages + std::runtime_error m; +}; + +/*! +@brief exception indicating a parse error + +This exception is thrown by the library when a parse error occurs. Parse errors +can occur during the deserialization of JSON text, CBOR, MessagePack, as well +as when using JSON Patch. + +Member @a byte holds the byte index of the last read character in the input +file. + +Exceptions have ids 1xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. +json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. +json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. +json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. +json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. +json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. +json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. +json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. +json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. +json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. +json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. + +@note For an input with n bytes, 1 is the index of the first character and n+1 + is the index of the terminating null byte or the end of file. This also + holds true when reading a byte vector (CBOR or MessagePack). + +@liveexample{The following code shows how a `parse_error` exception can be +caught.,parse_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class parse_error : public exception +{ + public: + /*! + @brief create a parse error exception + @param[in] id_ the id of the exception + @param[in] byte_ the byte index where the error occurred (or 0 if the + position cannot be determined) + @param[in] what_arg the explanatory string + @return parse_error object + */ + static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + (byte_ != 0 ? (" at " + std::to_string(byte_)) : "") + + ": " + what_arg; + return parse_error(id_, byte_, w.c_str()); + } + + /*! + @brief byte index of the parse error + + The byte index of the last read character in the input file. + + @note For an input with n bytes, 1 is the index of the first character and + n+1 is the index of the terminating null byte or the end of file. + This also holds true when reading a byte vector (CBOR or MessagePack). + */ + const std::size_t byte; + + private: + parse_error(int id_, std::size_t byte_, const char* what_arg) + : exception(id_, what_arg), byte(byte_) {} +}; + +/*! +@brief exception indicating errors with iterators + +This exception is thrown if iterators passed to a library function do not match +the expected semantics. + +Exceptions have ids 2xx. + +name / id | example message | description +----------------------------------- | --------------- | ------------------------- +json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. +json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. +json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. +json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. +json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. +json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. +json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. +json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. +json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. +json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). + +@liveexample{The following code shows how an `invalid_iterator` exception can be +caught.,invalid_iterator} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class invalid_iterator : public exception +{ + public: + static invalid_iterator create(int id_, const std::string& what_arg) + { + std::string w = exception::name("invalid_iterator", id_) + what_arg; + return invalid_iterator(id_, w.c_str()); + } + + private: + invalid_iterator(int id_, const char* what_arg) + : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating executing a member function with a wrong type + +This exception is thrown in case of a type error; that is, a library function is +executed on a JSON value whose type does not match the expected semantics. + +Exceptions have ids 3xx. + +name / id | example message | description +----------------------------- | --------------- | ------------------------- +json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. +json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. +json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t&. +json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. +json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. +json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. +json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. +json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. +json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. +json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. +json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. +json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. +json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. +json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. +json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. +json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | + +@liveexample{The following code shows how a `type_error` exception can be +caught.,type_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class type_error : public exception +{ + public: + static type_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("type_error", id_) + what_arg; + return type_error(id_, w.c_str()); + } + + private: + type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating access out of the defined range + +This exception is thrown in case a library function is called on an input +parameter that exceeds the expected range, for instance in case of array +indices or nonexisting object keys. + +Exceptions have ids 4xx. + +name / id | example message | description +------------------------------- | --------------- | ------------------------- +json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. +json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. +json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. +json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. +json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. +json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. +json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON only supports integers numbers up to 9223372036854775807. | +json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | + +@liveexample{The following code shows how an `out_of_range` exception can be +caught.,out_of_range} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class out_of_range : public exception +{ + public: + static out_of_range create(int id_, const std::string& what_arg) + { + std::string w = exception::name("out_of_range", id_) + what_arg; + return out_of_range(id_, w.c_str()); + } + + private: + out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating other library errors + +This exception is thrown in case of errors that cannot be classified with the +other exception types. + +Exceptions have ids 5xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range + +@liveexample{The following code shows how an `other_error` exception can be +caught.,other_error} + +@since version 3.0.0 +*/ +class other_error : public exception +{ + public: + static other_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("other_error", id_) + what_arg; + return other_error(id_, w.c_str()); + } + + private: + other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; +} +} + +// #include + + +#include // array +#include // and +#include // size_t +#include // uint8_t + +namespace nlohmann +{ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + discarded ///< discarded by the the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string +- furthermore, each type is not smaller than itself +- discarded values are not comparable + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); + return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index]; +} +} +} + +// #include + + +#include // transform +#include // array +#include // and, not +#include // forward_list +#include // inserter, front_inserter, end +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // pair, declval +#include // valarray + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// overloads for basic_json template parameters +template::value and + not std::is_same::value, + int> = 0> +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (JSON_UNLIKELY(not j.is_boolean())) + { + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); + } + b = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (JSON_UNLIKELY(not j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); + } + s = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, EnumType& e) +{ + typename std::underlying_type::type val; + get_arithmetic_value(j, val); + e = static_cast(val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + arr = *j.template get_ptr(); +} + +// forward_list doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::forward_list& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + std::transform(j.rbegin(), j.rend(), + std::front_inserter(l), [](const BasicJsonType & i) + { + return i.template get(); + }); +} + +// valarray doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::valarray& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + l.resize(j.size()); + std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l)); +} + +template +void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0> /*unused*/) +{ + using std::end; + + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) +-> decltype( + arr.reserve(std::declval()), + void()) +{ + using std::end; + + arr.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +void from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get(); + } +} + +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < + is_compatible_array_type::value and + not std::is_same::value and + std::is_constructible < + BasicJsonType, typename CompatibleArrayType::value_type >::value, + int > = 0 > +void from_json(const BasicJsonType& j, CompatibleArrayType& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + + std::string(j.type_name()))); + } + + from_json_array_impl(j, arr, priority_tag<2> {}); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +{ + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); + } + + auto inner_object = j.template get_ptr(); + using value_type = typename CompatibleObjectType::value_type; + std::transform( + inner_object->begin(), inner_object->end(), + std::inserter(obj, obj.begin()), + [](typename BasicJsonType::object_t::value_type const & p) + { + return value_type(p.first, p.second.template get()); + }); +} + +// overload for arithmetic types, not chosen for basic_json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for boolean_t etc. in case of a custom BooleanType which is not +// an arithmetic type? +template::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value, + int> = 0> +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::boolean: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, std::pair& p) +{ + p = {j.at(0).template get(), j.at(1).template get()}; +} + +template +void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence) +{ + t = std::make_tuple(j.at(Idx).template get::type>()...); +} + +template +void from_json(const BasicJsonType& j, std::tuple& t) +{ + from_json_tuple_impl(j, t, index_sequence_for {}); +} + +struct from_json_fn +{ + private: + template + auto call(const BasicJsonType& j, T& val, priority_tag<1> /*unused*/) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } + + template + void call(const BasicJsonType& /*unused*/, T& /*unused*/, priority_tag<0> /*unused*/) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find from_json() method in T's namespace"); +#ifdef _MSC_VER + // MSVC does not show a stacktrace for the above assert + using decayed = uncvref_t; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif + } + + public: + template + void operator()(const BasicJsonType& j, T& val) const + noexcept(noexcept(std::declval().call(j, val, priority_tag<1> {}))) + { + return call(j, val, priority_tag<1> {}); + } +}; +} + +/// namespace to hold default `from_json` function +/// to see why this is required: +/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html +namespace +{ +constexpr const auto& from_json = detail::static_const::value; +} +} + +// #include + + +#include // or, and, not +#include // begin, end +#include // tuple, get +#include // is_same, is_constructible, is_floating_point, is_enum, underlying_type +#include // move, forward, declval, pair +#include // valarray +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +////////////////// +// constructors // +////////////////// + +template struct external_constructor; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept + { + j.m_type = value_t::boolean; + j.m_value = b; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) + { + j.m_type = value_t::string; + j.m_value = s; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) + { + j.m_type = value_t::string; + j.m_value = std::move(s); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept + { + j.m_type = value_t::number_float; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept + { + j.m_type = value_t::number_unsigned; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept + { + j.m_type = value_t::number_integer; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) + { + j.m_type = value_t::array; + j.m_value = std::move(arr); + j.assert_invariant(); + } + + template::value, + int> = 0> + static void construct(BasicJsonType& j, const CompatibleArrayType& arr) + { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = j.template create(begin(arr), end(arr)); + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, const std::vector& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->reserve(arr.size()); + for (const bool x : arr) + { + j.m_value.array->push_back(x); + } + j.assert_invariant(); + } + + template::value, int> = 0> + static void construct(BasicJsonType& j, const std::valarray& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->resize(arr.size()); + std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) + { + j.m_type = value_t::object; + j.m_value = obj; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) + { + j.m_type = value_t::object; + j.m_value = std::move(obj); + j.assert_invariant(); + } + + template::value, int> = 0> + static void construct(BasicJsonType& j, const CompatibleObjectType& obj) + { + using std::begin; + using std::end; + + j.m_type = value_t::object; + j.m_value.object = j.template create(begin(obj), end(obj)); + j.assert_invariant(); + } +}; + +///////////// +// to_json // +///////////// + +template::value, int> = 0> +void to_json(BasicJsonType& j, T b) noexcept +{ + external_constructor::construct(j, b); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleString& s) +{ + external_constructor::construct(j, s); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +{ + external_constructor::construct(j, std::move(s)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, FloatType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, EnumType e) noexcept +{ + using underlying_type = typename std::underlying_type::type; + external_constructor::construct(j, static_cast(e)); +} + +template +void to_json(BasicJsonType& j, const std::vector& e) +{ + external_constructor::construct(j, e); +} + +template::value or + std::is_same::value, + int> = 0> +void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +{ + external_constructor::construct(j, arr); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, std::valarray arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +{ + external_constructor::construct(j, obj); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +{ + external_constructor::construct(j, std::move(obj)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, T (&arr)[N]) +{ + external_constructor::construct(j, arr); +} + +template +void to_json(BasicJsonType& j, const std::pair& p) +{ + j = {p.first, p.second}; +} + +template +void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence) +{ + j = {std::get(t)...}; +} + +template +void to_json(BasicJsonType& j, const std::tuple& t) +{ + to_json_tuple_impl(j, t, index_sequence_for {}); +} + +struct to_json_fn +{ + private: + template + auto call(BasicJsonType& j, T&& val, priority_tag<1> /*unused*/) const noexcept(noexcept(to_json(j, std::forward(val)))) + -> decltype(to_json(j, std::forward(val)), void()) + { + return to_json(j, std::forward(val)); + } + + template + void call(BasicJsonType& /*unused*/, T&& /*unused*/, priority_tag<0> /*unused*/) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find to_json() method in T's namespace"); + +#ifdef _MSC_VER + // MSVC does not show a stacktrace for the above assert + using decayed = uncvref_t; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif + } + + public: + template + void operator()(BasicJsonType& j, T&& val) const + noexcept(noexcept(std::declval().call(j, std::forward(val), priority_tag<1> {}))) + { + return call(j, std::forward(val), priority_tag<1> {}); + } +}; +} + +/// namespace to hold default `to_json` function +namespace +{ +constexpr const auto& to_json = detail::static_const::value; +} +} + +// #include + + +#include // min +#include // array +#include // assert +#include // size_t +#include // strlen +#include // streamsize, streamoff, streampos +#include // istream +#include // begin, end, iterator_traits, random_access_iterator_tag, distance, next +#include // shared_ptr, make_shared, addressof +#include // accumulate +#include // string, char_traits +#include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer +#include // pair, declval + +// #include + + +namespace nlohmann +{ +namespace detail +{ +//////////////////// +// input adapters // +//////////////////// + +/*! +@brief abstract input adapter interface + +Produces a stream of std::char_traits::int_type characters from a +std::istream, a buffer, or some other input type. Accepts the return of exactly +one non-EOF character for future input. The int_type characters returned +consist of all valid char values as positive values (typically unsigned char), +plus an EOF value outside that range, specified by the value of the function +std::char_traits::eof(). This value is typically -1, but could be any +arbitrary value which is not a valid char value. +*/ +struct input_adapter_protocol +{ + /// get a character [0,255] or std::char_traits::eof(). + virtual std::char_traits::int_type get_character() = 0; + /// restore the last non-eof() character to input + virtual void unget_character() = 0; + virtual ~input_adapter_protocol() = default; +}; + +/// a type to simplify interfaces +using input_adapter_t = std::shared_ptr; + +/*! +Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at +beginning of input. Does not support changing the underlying std::streambuf +in mid-input. Maintains underlying std::istream and std::streambuf to support +subsequent use of standard std::istream operations to process any input +characters following those used in parsing the JSON input. Clears the +std::istream flags; any input errors (e.g., EOF) will be detected by the first +subsequent call for input from the std::istream. +*/ +class input_stream_adapter : public input_adapter_protocol +{ + public: + ~input_stream_adapter() override + { + // clear stream flags; we use underlying streambuf I/O, do not + // maintain ifstream flags + is.clear(); + } + + explicit input_stream_adapter(std::istream& i) + : is(i), sb(*i.rdbuf()) + { + // skip byte order mark + std::char_traits::int_type c; + if ((c = get_character()) == 0xEF) + { + if ((c = get_character()) == 0xBB) + { + if ((c = get_character()) == 0xBF) + { + return; // Ignore BOM + } + else if (c != std::char_traits::eof()) + { + is.unget(); + } + is.putback('\xBB'); + } + else if (c != std::char_traits::eof()) + { + is.unget(); + } + is.putback('\xEF'); + } + else if (c != std::char_traits::eof()) + { + is.unget(); // no byte order mark; process as usual + } + } + + // delete because of pointer members + input_stream_adapter(const input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&) = delete; + + // std::istream/std::streambuf use std::char_traits::to_int_type, to + // ensure that std::char_traits::eof() and the character 0xFF do not + // end up as the same value, eg. 0xFFFFFFFF. + std::char_traits::int_type get_character() override + { + return sb.sbumpc(); + } + + void unget_character() override + { + sb.sungetc(); // is.unget() avoided for performance + } + + private: + /// the associated input stream + std::istream& is; + std::streambuf& sb; +}; + +/// input adapter for buffer input +class input_buffer_adapter : public input_adapter_protocol +{ + public: + input_buffer_adapter(const char* b, const std::size_t l) + : cursor(b), limit(b + l), start(b) + { + // skip byte order mark + if (l >= 3 and b[0] == '\xEF' and b[1] == '\xBB' and b[2] == '\xBF') + { + cursor += 3; + } + } + + // delete because of pointer members + input_buffer_adapter(const input_buffer_adapter&) = delete; + input_buffer_adapter& operator=(input_buffer_adapter&) = delete; + + std::char_traits::int_type get_character() noexcept override + { + if (JSON_LIKELY(cursor < limit)) + { + return std::char_traits::to_int_type(*(cursor++)); + } + + return std::char_traits::eof(); + } + + void unget_character() noexcept override + { + if (JSON_LIKELY(cursor > start)) + { + --cursor; + } + } + + private: + /// pointer to the current character + const char* cursor; + /// pointer past the last character + const char* limit; + /// pointer to the first character + const char* start; +}; + +class input_adapter +{ + public: + // native support + + /// input adapter for input stream + input_adapter(std::istream& i) + : ia(std::make_shared(i)) {} + + /// input adapter for input stream + input_adapter(std::istream&& i) + : ia(std::make_shared(i)) {} + + /// input adapter for buffer + template::value and + std::is_integral::type>::value and + sizeof(typename std::remove_pointer::type) == 1, + int>::type = 0> + input_adapter(CharT b, std::size_t l) + : ia(std::make_shared(reinterpret_cast(b), l)) {} + + // derived support + + /// input adapter for string literal + template::value and + std::is_integral::type>::value and + sizeof(typename std::remove_pointer::type) == 1, + int>::type = 0> + input_adapter(CharT b) + : input_adapter(reinterpret_cast(b), + std::strlen(reinterpret_cast(b))) {} + + /// input adapter for iterator range with contiguous storage + template::iterator_category, std::random_access_iterator_tag>::value, + int>::type = 0> + input_adapter(IteratorType first, IteratorType last) + { + // assertion to check that the iterator range is indeed contiguous, + // see http://stackoverflow.com/a/35008842/266378 for more discussion + assert(std::accumulate( + first, last, std::pair(true, 0), + [&first](std::pair res, decltype(*first) val) + { + res.first &= (val == *(std::next(std::addressof(*first), res.second++))); + return res; + }).first); + + // assertion to check that each element is 1 byte long + static_assert( + sizeof(typename std::iterator_traits::value_type) == 1, + "each element in the iterator range must have the size of 1 byte"); + + const auto len = static_cast(std::distance(first, last)); + if (JSON_LIKELY(len > 0)) + { + // there is at least one element: use the address of first + ia = std::make_shared(reinterpret_cast(&(*first)), len); + } + else + { + // the address of first cannot be used: use nullptr + ia = std::make_shared(nullptr, len); + } + } + + /// input adapter for array + template + input_adapter(T (&array)[N]) + : input_adapter(std::begin(array), std::end(array)) {} + + /// input adapter for contiguous container + template::value and + std::is_base_of()))>::iterator_category>::value, + int>::type = 0> + input_adapter(const ContiguousContainer& c) + : input_adapter(std::begin(c), std::end(c)) {} + + operator input_adapter_t() + { + return ia; + } + + private: + /// the actual adapter + input_adapter_t ia = nullptr; +}; +} +} + +// #include + + +#include // localeconv +#include // size_t +#include // strtof, strtod, strtold, strtoll, strtoull +#include // initializer_list +#include // hex, uppercase +#include // setw, setfill +#include // stringstream +#include // char_traits, string +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////// +// lexer // +/////////// + +/*! +@brief lexical analysis + +This class organizes the lexical analysis during JSON deserialization. +*/ +template +class lexer +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value + value_integer, ///< a signed integer -- use get_number_integer() for actual value + value_float, ///< an floating point number -- use get_number_float() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input, ///< indicating the end of the input buffer + literal_or_value ///< a literal or the begin of a value (only for diagnostics) + }; + + /// return name of values of type token_type (only used for errors) + static const char* token_type_name(const token_type t) noexcept + { + switch (t) + { + case token_type::uninitialized: + return ""; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case lexer::token_type::value_unsigned: + case lexer::token_type::value_integer: + case lexer::token_type::value_float: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return ""; + case token_type::end_of_input: + return "end of input"; + case token_type::literal_or_value: + return "'[', '{', or a literal"; + default: // catch non-enum values + return "unknown token"; // LCOV_EXCL_LINE + } + } + + explicit lexer(detail::input_adapter_t adapter) + : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {} + + // delete because of pointer members + lexer(const lexer&) = delete; + lexer& operator=(lexer&) = delete; + + private: + ///////////////////// + // locales + ///////////////////// + + /// return the locale-dependent decimal point + static char get_decimal_point() noexcept + { + const auto loc = localeconv(); + assert(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); + } + + ///////////////////// + // scan functions + ///////////////////// + + /*! + @brief get codepoint from 4 hex characters following `\u` + + For input "\u c1 c2 c3 c4" the codepoint is: + (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 + = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) + + Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' + must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The + conversion is done by subtracting the offset (0x30, 0x37, and 0x57) + between the ASCII value of the character and the desired integer value. + + @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or + non-hex character) + */ + int get_codepoint() + { + // this function only makes sense after reading `\u` + assert(current == 'u'); + int codepoint = 0; + + const auto factors = { 12, 8, 4, 0 }; + for (const auto factor : factors) + { + get(); + + if (current >= '0' and current <= '9') + { + codepoint += ((current - 0x30) << factor); + } + else if (current >= 'A' and current <= 'F') + { + codepoint += ((current - 0x37) << factor); + } + else if (current >= 'a' and current <= 'f') + { + codepoint += ((current - 0x57) << factor); + } + else + { + return -1; + } + } + + assert(0x0000 <= codepoint and codepoint <= 0xFFFF); + return codepoint; + } + + /*! + @brief check if the next byte(s) are inside a given range + + Adds the current byte and, for each passed range, reads a new byte and + checks if it is inside the range. If a violation was detected, set up an + error message and return false. Otherwise, return true. + + @param[in] ranges list of integers; interpreted as list of pairs of + inclusive lower and upper bound, respectively + + @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, + 1, 2, or 3 pairs. This precondition is enforced by an assertion. + + @return true if and only if no range violation was detected + */ + bool next_byte_in_range(std::initializer_list ranges) + { + assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6); + add(current); + + for (auto range = ranges.begin(); range != ranges.end(); ++range) + { + get(); + if (JSON_LIKELY(*range <= current and current <= *(++range))) + { + add(current); + } + else + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; + } + } + + return true; + } + + /*! + @brief scan a string literal + + This function scans a string according to Sect. 7 of RFC 7159. While + scanning, bytes are escaped and copied into buffer token_buffer. Then the + function returns successfully, token_buffer is *not* null-terminated (as it + may contain \0 bytes), and token_buffer.size() is the number of bytes in the + string. + + @return token_type::value_string if string could be successfully scanned, + token_type::parse_error otherwise + + @note In case of errors, variable error_message contains a textual + description. + */ + token_type scan_string() + { + // reset token_buffer (ignore opening quote) + reset(); + + // we entered the function by reading an open quote + assert(current == '\"'); + + while (true) + { + // get next character + switch (get()) + { + // end of file while parsing string + case std::char_traits::eof(): + { + error_message = "invalid string: missing closing quote"; + return token_type::parse_error; + } + + // closing quote + case '\"': + { + return token_type::value_string; + } + + // escapes + case '\\': + { + switch (get()) + { + // quotation mark + case '\"': + add('\"'); + break; + // reverse solidus + case '\\': + add('\\'); + break; + // solidus + case '/': + add('/'); + break; + // backspace + case 'b': + add('\b'); + break; + // form feed + case 'f': + add('\f'); + break; + // line feed + case 'n': + add('\n'); + break; + // carriage return + case 'r': + add('\r'); + break; + // tab + case 't': + add('\t'); + break; + + // unicode escapes + case 'u': + { + const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; // start with codepoint1 + + if (JSON_UNLIKELY(codepoint1 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if code point is a high surrogate + if (0xD800 <= codepoint1 and codepoint1 <= 0xDBFF) + { + // expect next \uxxxx entry + if (JSON_LIKELY(get() == '\\' and get() == 'u')) + { + const int codepoint2 = get_codepoint(); + + if (JSON_UNLIKELY(codepoint2 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if codepoint2 is a low surrogate + if (JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF)) + { + // overwrite codepoint + codepoint = + // high surrogate occupies the most significant 22 bits + (codepoint1 << 10) + // low surrogate occupies the least significant 15 bits + + codepoint2 + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00; + } + else + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + if (JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF)) + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; + return token_type::parse_error; + } + } + + // result of the above calculation yields a proper codepoint + assert(0x00 <= codepoint and codepoint <= 0x10FFFF); + + // translate codepoint into bytes + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + add(codepoint); + } + else if (codepoint <= 0x7FF) + { + // 2-byte characters: 110xxxxx 10xxxxxx + add(0xC0 | (codepoint >> 6)); + add(0x80 | (codepoint & 0x3F)); + } + else if (codepoint <= 0xFFFF) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + add(0xE0 | (codepoint >> 12)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } + else + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + add(0xF0 | (codepoint >> 18)); + add(0x80 | ((codepoint >> 12) & 0x3F)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } + + break; + } + + // other characters after escape + default: + error_message = "invalid string: forbidden character after backslash"; + return token_type::parse_error; + } + + break; + } + + // invalid control characters + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + { + error_message = "invalid string: control character must be escaped"; + return token_type::parse_error; + } + + // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) + case 0x20: + case 0x21: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + { + add(current); + break; + } + + // U+0080..U+07FF: bytes C2..DF 80..BF + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: + { + if (JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF}))) + { + return token_type::parse_error; + } + break; + } + + // U+0800..U+0FFF: bytes E0 A0..BF 80..BF + case 0xE0: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF + // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xEE: + case 0xEF: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+D000..U+D7FF: bytes ED 80..9F 80..BF + case 0xED: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF + case 0xF0: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF + case 0xF1: + case 0xF2: + case 0xF3: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF + case 0xF4: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // remaining bytes (80..C1 and F5..FF) are ill-formed + default: + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return token_type::parse_error; + } + } + } + } + + static void strtof(float& f, const char* str, char** endptr) noexcept + { + f = std::strtof(str, endptr); + } + + static void strtof(double& f, const char* str, char** endptr) noexcept + { + f = std::strtod(str, endptr); + } + + static void strtof(long double& f, const char* str, char** endptr) noexcept + { + f = std::strtold(str, endptr); + } + + /*! + @brief scan a number literal + + This function scans a string according to Sect. 6 of RFC 7159. + + The function is realized with a deterministic finite state machine derived + from the grammar described in RFC 7159. Starting in state "init", the + input is read and used to determined the next state. Only state "done" + accepts the number. State "error" is a trap state to model errors. In the + table below, "anything" means any character but the ones listed before. + + state | 0 | 1-9 | e E | + | - | . | anything + ---------|----------|----------|----------|---------|---------|----------|----------- + init | zero | any1 | [error] | [error] | minus | [error] | [error] + minus | zero | any1 | [error] | [error] | [error] | [error] | [error] + zero | done | done | exponent | done | done | decimal1 | done + any1 | any1 | any1 | exponent | done | done | decimal1 | done + decimal1 | decimal2 | [error] | [error] | [error] | [error] | [error] | [error] + decimal2 | decimal2 | decimal2 | exponent | done | done | done | done + exponent | any2 | any2 | [error] | sign | sign | [error] | [error] + sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] + any2 | any2 | any2 | done | done | done | done | done + + The state machine is realized with one label per state (prefixed with + "scan_number_") and `goto` statements between them. The state machine + contains cycles, but any cycle can be left when EOF is read. Therefore, + the function is guaranteed to terminate. + + During scanning, the read bytes are stored in token_buffer. This string is + then converted to a signed integer, an unsigned integer, or a + floating-point number. + + @return token_type::value_unsigned, token_type::value_integer, or + token_type::value_float if number could be successfully scanned, + token_type::parse_error otherwise + + @note The scanner is independent of the current locale. Internally, the + locale's decimal point is used instead of `.` to work with the + locale-dependent converters. + */ + token_type scan_number() + { + // reset token_buffer to store the number's bytes + reset(); + + // the type of the parsed number; initially set to unsigned; will be + // changed if minus sign, decimal point or exponent is read + token_type number_type = token_type::value_unsigned; + + // state (init): we just found out we need to scan a number + switch (current) + { + case '-': + { + add(current); + goto scan_number_minus; + } + + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + // all other characters are rejected outside scan_number() + assert(false); // LCOV_EXCL_LINE + } + } + +scan_number_minus: + // state: we just parsed a leading minus sign + number_type = token_type::value_integer; + switch (get()) + { + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + error_message = "invalid number; expected digit after '-'"; + return token_type::parse_error; + } + } + +scan_number_zero: + // state: we just parse a zero (maybe with a leading minus sign) + switch (get()) + { + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_any1: + // state: we just parsed a number 0-9 (maybe with a leading minus sign) + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_decimal1: + // state: we just parsed a decimal point + number_type = token_type::value_float; + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + default: + { + error_message = "invalid number; expected digit after '.'"; + return token_type::parse_error; + } + } + +scan_number_decimal2: + // we just parsed at least one number after a decimal point + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_exponent: + // we just parsed an exponent + number_type = token_type::value_float; + switch (get()) + { + case '+': + case '-': + { + add(current); + goto scan_number_sign; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = + "invalid number; expected '+', '-', or digit after exponent"; + return token_type::parse_error; + } + } + +scan_number_sign: + // we just parsed an exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = "invalid number; expected digit after exponent sign"; + return token_type::parse_error; + } + } + +scan_number_any2: + // we just parsed a number after the exponent or exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + goto scan_number_done; + } + +scan_number_done: + // unget the character after the number (we only read it to know that + // we are done scanning a number) + unget(); + + char* endptr = nullptr; + errno = 0; + + // try to parse integers first and fall back to floats + if (number_type == token_type::value_unsigned) + { + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_unsigned = static_cast(x); + if (value_unsigned == x) + { + return token_type::value_unsigned; + } + } + } + else if (number_type == token_type::value_integer) + { + const auto x = std::strtoll(token_buffer.data(), &endptr, 10); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_integer = static_cast(x); + if (value_integer == x) + { + return token_type::value_integer; + } + } + } + + // this code is reached if we parse a floating-point number or if an + // integer conversion above failed + strtof(value_float, token_buffer.data(), &endptr); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + + return token_type::value_float; + } + + /*! + @param[in] literal_text the literal text to expect + @param[in] length the length of the passed literal text + @param[in] return_type the token type to return on success + */ + token_type scan_literal(const char* literal_text, const std::size_t length, + token_type return_type) + { + assert(current == literal_text[0]); + for (std::size_t i = 1; i < length; ++i) + { + if (JSON_UNLIKELY(get() != literal_text[i])) + { + error_message = "invalid literal"; + return token_type::parse_error; + } + } + return return_type; + } + + ///////////////////// + // input management + ///////////////////// + + /// reset token_buffer; current character is beginning of token + void reset() noexcept + { + token_buffer.clear(); + token_string.clear(); + token_string.push_back(std::char_traits::to_char_type(current)); + } + + /* + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a + `std::char_traits::eof()` in that case. Stores the scanned characters + for use in error messages. + + @return character read from the input + */ + std::char_traits::int_type get() + { + ++chars_read; + current = ia->get_character(); + if (JSON_LIKELY(current != std::char_traits::eof())) + { + token_string.push_back(std::char_traits::to_char_type(current)); + } + return current; + } + + /// unget current character (return it again on next get) + void unget() + { + --chars_read; + if (JSON_LIKELY(current != std::char_traits::eof())) + { + ia->unget_character(); + assert(token_string.size() != 0); + token_string.pop_back(); + } + } + + /// add a character to token_buffer + void add(int c) + { + token_buffer.push_back(std::char_traits::to_char_type(c)); + } + + public: + ///////////////////// + // value getters + ///////////////////// + + /// return integer value + constexpr number_integer_t get_number_integer() const noexcept + { + return value_integer; + } + + /// return unsigned integer value + constexpr number_unsigned_t get_number_unsigned() const noexcept + { + return value_unsigned; + } + + /// return floating-point value + constexpr number_float_t get_number_float() const noexcept + { + return value_float; + } + + /// return current string value (implicitly resets the token; useful only once) + std::string move_string() + { + return std::move(token_buffer); + } + + ///////////////////// + // diagnostics + ///////////////////// + + /// return position of last read token + constexpr std::size_t get_position() const noexcept + { + return chars_read; + } + + /// return the last read token (for errors only). Will never contain EOF + /// (an arbitrary value that is not a valid char value, often -1), because + /// 255 may legitimately occur. May contain NUL, which should be escaped. + std::string get_token_string() const + { + // escape control characters + std::string result; + for (const auto c : token_string) + { + if ('\x00' <= c and c <= '\x1F') + { + // escape control characters + std::stringstream ss; + ss << "(c) << ">"; + result += ss.str(); + } + else + { + // add character as is + result.push_back(c); + } + } + + return result; + } + + /// return syntax error message + constexpr const char* get_error_message() const noexcept + { + return error_message; + } + + ///////////////////// + // actual scanner + ///////////////////// + + token_type scan() + { + // read next character and ignore whitespace + do + { + get(); + } + while (current == ' ' or current == '\t' or current == '\n' or current == '\r'); + + switch (current) + { + // structural characters + case '[': + return token_type::begin_array; + case ']': + return token_type::end_array; + case '{': + return token_type::begin_object; + case '}': + return token_type::end_object; + case ':': + return token_type::name_separator; + case ',': + return token_type::value_separator; + + // literals + case 't': + return scan_literal("true", 4, token_type::literal_true); + case 'f': + return scan_literal("false", 5, token_type::literal_false); + case 'n': + return scan_literal("null", 4, token_type::literal_null); + + // string + case '\"': + return scan_string(); + + // number + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scan_number(); + + // end of input (the null byte is needed when parsing from + // string literals) + case '\0': + case std::char_traits::eof(): + return token_type::end_of_input; + + // error + default: + error_message = "invalid literal"; + return token_type::parse_error; + } + } + + private: + /// input adapter + detail::input_adapter_t ia = nullptr; + + /// the current character + std::char_traits::int_type current = std::char_traits::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// raw input token string (for error messages) + std::vector token_string {}; + + /// buffer for variable-length tokens (numbers, strings) + std::string token_buffer {}; + + /// a description of occurred lexer errors + const char* error_message = ""; + + // number values + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; + + /// the decimal point + const char decimal_point_char = '.'; +}; +} +} + +// #include + + +#include // assert +#include // isfinite +#include // uint8_t +#include // function +#include // string +#include // move + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +//////////// +// parser // +//////////// + +/*! +@brief syntax analysis + +This class implements a recursive decent parser. +*/ +template +class parser +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using lexer_t = lexer; + using token_type = typename lexer_t::token_type; + + public: + enum class parse_event_t : uint8_t + { + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value + }; + + using parser_callback_t = + std::function; + + /// a parser reading from an input adapter + explicit parser(detail::input_adapter_t adapter, + const parser_callback_t cb = nullptr, + const bool allow_exceptions_ = true) + : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_) + {} + + /*! + @brief public parser interface + + @param[in] strict whether to expect the last token to be EOF + @param[in,out] result parsed JSON value + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse(const bool strict, BasicJsonType& result) + { + // read first token + get_token(); + + parse_internal(true, result); + result.assert_invariant(); + + // in strict mode, input must be completely read + if (strict) + { + get_token(); + expect(token_type::end_of_input); + } + + // in case of an error, return discarded value + if (errored) + { + result = value_t::discarded; + return; + } + + // set top-level value to null if it was discarded by the callback + // function + if (result.is_discarded()) + { + result = nullptr; + } + } + + /*! + @brief public accept interface + + @param[in] strict whether to expect the last token to be EOF + @return whether the input is a proper JSON text + */ + bool accept(const bool strict = true) + { + // read first token + get_token(); + + if (not accept_internal()) + { + return false; + } + + // strict => last token must be EOF + return not strict or (get_token() == token_type::end_of_input); + } + + private: + /*! + @brief the actual parser + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse_internal(bool keep, BasicJsonType& result) + { + // never parse after a parse error was detected + assert(not errored); + + // start with a discarded value + if (not result.is_discarded()) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + + switch (last_token) + { + case token_type::begin_object: + { + if (keep) + { + if (callback) + { + keep = callback(depth++, parse_event_t::object_start, result); + } + + if (not callback or keep) + { + // explicitly set result to object to cope with {} + result.m_type = value_t::object; + result.m_value = value_t::object; + } + } + + // read next token + get_token(); + + // closing } -> we are done + if (last_token == token_type::end_object) + { + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + // parse values + std::string key; + BasicJsonType value; + while (true) + { + // store key + if (not expect(token_type::value_string)) + { + return; + } + key = m_lexer.move_string(); + + bool keep_tag = false; + if (keep) + { + if (callback) + { + BasicJsonType k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } + else + { + keep_tag = true; + } + } + + // parse separator (:) + get_token(); + if (not expect(token_type::name_separator)) + { + return; + } + + // parse and add value + get_token(); + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if (JSON_UNLIKELY(errored)) + { + return; + } + + if (keep and keep_tag and not value.is_discarded()) + { + result.m_value.object->emplace(std::move(key), std::move(value)); + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing } + if (not expect(token_type::end_object)) + { + return; + } + break; + } + + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + case token_type::begin_array: + { + if (keep) + { + if (callback) + { + keep = callback(depth++, parse_event_t::array_start, result); + } + + if (not callback or keep) + { + // explicitly set result to array to cope with [] + result.m_type = value_t::array; + result.m_value = value_t::array; + } + } + + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == token_type::end_array) + { + if (callback and not callback(--depth, parse_event_t::array_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + // parse values + BasicJsonType value; + while (true) + { + // parse value + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if (JSON_UNLIKELY(errored)) + { + return; + } + + if (keep and not value.is_discarded()) + { + result.m_value.array->push_back(std::move(value)); + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing ] + if (not expect(token_type::end_array)) + { + return; + } + break; + } + + if (keep and callback and not callback(--depth, parse_event_t::array_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + case token_type::literal_null: + { + result.m_type = value_t::null; + break; + } + + case token_type::value_string: + { + result.m_type = value_t::string; + result.m_value = m_lexer.move_string(); + break; + } + + case token_type::literal_true: + { + result.m_type = value_t::boolean; + result.m_value = true; + break; + } + + case token_type::literal_false: + { + result.m_type = value_t::boolean; + result.m_value = false; + break; + } + + case token_type::value_unsigned: + { + result.m_type = value_t::number_unsigned; + result.m_value = m_lexer.get_number_unsigned(); + break; + } + + case token_type::value_integer: + { + result.m_type = value_t::number_integer; + result.m_value = m_lexer.get_number_integer(); + break; + } + + case token_type::value_float: + { + result.m_type = value_t::number_float; + result.m_value = m_lexer.get_number_float(); + + // throw in case of infinity or NAN + if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float))) + { + if (allow_exceptions) + { + JSON_THROW(out_of_range::create(406, "number overflow parsing '" + + m_lexer.get_token_string() + "'")); + } + expect(token_type::uninitialized); + } + break; + } + + case token_type::parse_error: + { + // using "uninitialized" to avoid "expected" message + if (not expect(token_type::uninitialized)) + { + return; + } + break; // LCOV_EXCL_LINE + } + + default: + { + // the last token was unexpected; we expected a value + if (not expect(token_type::literal_or_value)) + { + return; + } + break; // LCOV_EXCL_LINE + } + } + + if (keep and callback and not callback(depth, parse_event_t::value, result)) + { + result.m_type = value_t::discarded; + } + } + + /*! + @brief the actual acceptor + + @invariant 1. The last token is not yet processed. Therefore, the caller + of this function must make sure a token has been read. + 2. When this function returns, the last token is processed. + That is, the last read character was already considered. + + This invariant makes sure that no token needs to be "unput". + */ + bool accept_internal() + { + switch (last_token) + { + case token_type::begin_object: + { + // read next token + get_token(); + + // closing } -> we are done + if (last_token == token_type::end_object) + { + return true; + } + + // parse values + while (true) + { + // parse key + if (last_token != token_type::value_string) + { + return false; + } + + // parse separator (:) + get_token(); + if (last_token != token_type::name_separator) + { + return false; + } + + // parse value + get_token(); + if (not accept_internal()) + { + return false; + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing } + return (last_token == token_type::end_object); + } + } + + case token_type::begin_array: + { + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == token_type::end_array) + { + return true; + } + + // parse values + while (true) + { + // parse value + if (not accept_internal()) + { + return false; + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing ] + return (last_token == token_type::end_array); + } + } + + case token_type::value_float: + { + // reject infinity or NAN + return std::isfinite(m_lexer.get_number_float()); + } + + case token_type::literal_false: + case token_type::literal_null: + case token_type::literal_true: + case token_type::value_integer: + case token_type::value_string: + case token_type::value_unsigned: + return true; + + default: // the last token was unexpected + return false; + } + } + + /// get next token from lexer + token_type get_token() + { + return (last_token = m_lexer.scan()); + } + + /*! + @throw parse_error.101 if expected token did not occur + */ + bool expect(token_type t) + { + if (JSON_UNLIKELY(t != last_token)) + { + errored = true; + expected = t; + if (allow_exceptions) + { + throw_exception(); + } + else + { + return false; + } + } + + return true; + } + + [[noreturn]] void throw_exception() const + { + std::string error_msg = "syntax error - "; + if (last_token == token_type::parse_error) + { + error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + + m_lexer.get_token_string() + "'"; + } + else + { + error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); + } + + if (expected != token_type::uninitialized) + { + error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); + } + + JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg)); + } + + private: + /// current level of recursion + int depth = 0; + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + token_type last_token = token_type::uninitialized; + /// the lexer + lexer_t m_lexer; + /// whether a syntax error occurred + bool errored = false; + /// possible reason for the syntax error + token_type expected = token_type::uninitialized; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; +} +} + +// #include + + +#include // ptrdiff_t +#include // numeric_limits + +namespace nlohmann +{ +namespace detail +{ +/* +@brief an iterator for primitive JSON types + +This class models an iterator for primitive JSON types (boolean, number, +string). It's only purpose is to allow the iterator/const_iterator classes +to "iterate" over primitive values. Internally, the iterator is modeled by +a `difference_type` variable. Value begin_value (`0`) models the begin, +end_value (`1`) models past the end. +*/ +class primitive_iterator_t +{ + private: + using difference_type = std::ptrdiff_t; + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + /// iterator as signed integer type + difference_type m_it = (std::numeric_limits::min)(); + + public: + constexpr difference_type get_value() const noexcept + { + return m_it; + } + + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return m_it == begin_value; + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return m_it == end_value; + } + + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it == rhs.m_it; + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + primitive_iterator_t operator+(difference_type n) noexcept + { + auto result = *this; + result += n; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + primitive_iterator_t& operator++() noexcept + { + ++m_it; + return *this; + } + + primitive_iterator_t const operator++(int) noexcept + { + auto result = *this; + m_it++; + return result; + } + + primitive_iterator_t& operator--() noexcept + { + --m_it; + return *this; + } + + primitive_iterator_t const operator--(int) noexcept + { + auto result = *this; + m_it--; + return result; + } + + primitive_iterator_t& operator+=(difference_type n) noexcept + { + m_it += n; + return *this; + } + + primitive_iterator_t& operator-=(difference_type n) noexcept + { + m_it -= n; + return *this; + } +}; +} +} + +// #include + + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/*! +@brief an iterator value + +@note This structure could easily be a union, but MSVC currently does not allow +unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. +*/ +template struct internal_iterator +{ + /// iterator for JSON objects + typename BasicJsonType::object_t::iterator object_iterator {}; + /// iterator for JSON arrays + typename BasicJsonType::array_t::iterator array_iterator {}; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator {}; +}; +} +} + +// #include + + +#include // not +#include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next +#include // conditional, is_const, remove_const + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// forward declare, to be able to friend it later on +template class iteration_proxy; + +/*! +@brief a template for a bidirectional iterator for the @ref basic_json class + +This class implements a both iterators (iterator and const_iterator) for the +@ref basic_json class. + +@note An iterator is called *initialized* when a pointer to a JSON value has + been set (e.g., by a constructor or a copy assignment). If the iterator is + default-constructed, it is *uninitialized* and most methods are undefined. + **The library uses assertions to detect calls on uninitialized iterators.** + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). + +@since version 1.0.0, simplified in version 2.0.9, change to bidirectional + iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) +*/ +template +class iter_impl +{ + /// allow basic_json to access private members + friend iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; + friend BasicJsonType; + friend iteration_proxy; + + using object_t = typename BasicJsonType::object_t; + using array_t = typename BasicJsonType::array_t; + // make sure BasicJsonType is basic_json or const basic_json + static_assert(is_basic_json::type>::value, + "iter_impl only accepts (const) basic_json"); + + public: + + /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. + /// The C++ Standard has never required user-defined iterators to derive from std::iterator. + /// A user-defined iterator should provide publicly accessible typedefs named + /// iterator_category, value_type, difference_type, pointer, and reference. + /// Note that value_type is required to be non-const, even for constant iterators. + using iterator_category = std::bidirectional_iterator_tag; + + /// the type of the values when the iterator is dereferenced + using value_type = typename BasicJsonType::value_type; + /// a type to represent differences between iterators + using difference_type = typename BasicJsonType::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename std::conditional::value, + typename BasicJsonType::const_pointer, + typename BasicJsonType::pointer>::type; + /// defines a reference to the type iterated over (value_type) + using reference = + typename std::conditional::value, + typename BasicJsonType::const_reference, + typename BasicJsonType::reference>::type; + + /// default constructor + iter_impl() = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit iter_impl(pointer object) noexcept : m_object(object) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /*! + @note The conventional copy constructor and copy assignment are implicitly + defined. Combined with the following converting constructor and + assignment, they support: (1) copy from iterator to iterator, (2) + copy from const iterator to const iterator, and (3) conversion from + iterator to const iterator. However conversion from const iterator + to iterator is not defined. + */ + + /*! + @brief converting constructor + @param[in] other non-const iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl(const iter_impl::type>& other) noexcept + : m_object(other.m_object), m_it(other.m_it) {} + + /*! + @brief converting assignment + @param[in,out] other non-const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl::type>& other) noexcept + { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + + private: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) + { + return m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator++(int) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, 1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator--(int) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, -1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator==(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + return (m_it.object_iterator == other.m_it.object_iterator); + + case value_t::array: + return (m_it.array_iterator == other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator!=(const iter_impl& other) const + { + return not operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); + + case value_t::array: + return (m_it.array_iterator < other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const iter_impl& other) const + { + return not other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const iter_impl& other) const + { + return not operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const iter_impl& other) const + { + return not operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief addition of distance and iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + friend iter_impl operator+(difference_type i, const iter_impl& it) + { + auto result = it; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const iter_impl& other) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + return m_it.array_iterator - other.m_it.array_iterator; + + default: + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); + + case value_t::array: + return *std::next(m_it.array_iterator, n); + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.get_value() == -n)) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + typename object_t::key_type key() const + { + assert(m_object != nullptr); + + if (JSON_LIKELY(m_object->is_object())) + { + return m_it.object_iterator->first; + } + + JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + private: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator::type> m_it; +}; +} +} + +// #include + + +#include // size_t +#include // string, to_string + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/// proxy class for the items() function +template class iteration_proxy +{ + private: + /// helper class for iteration + class iteration_proxy_internal + { + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + std::size_t array_index = 0; + + public: + explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {} + + /// dereference operator (needed for range-based for) + iteration_proxy_internal& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_internal& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// inequality operator (needed for range-based for) + bool operator!=(const iteration_proxy_internal& o) const noexcept + { + return anchor != o.anchor; + } + + /// return key of the iterator + std::string key() const + { + assert(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + return std::to_string(array_index); + + // use key from the object + case value_t::object: + return anchor.key(); + + // use an empty key for all primitive types + default: + return ""; + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } + }; + + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) noexcept + : container(cont) {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_internal begin() noexcept + { + return iteration_proxy_internal(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_internal end() noexcept + { + return iteration_proxy_internal(container.end()); + } +}; +} +} + +// #include + + +#include // ptrdiff_t +#include // reverse_iterator +#include // declval + +namespace nlohmann +{ +namespace detail +{ +////////////////////// +// reverse_iterator // +////////////////////// + +/*! +@brief a template for a reverse iterator class + +@tparam Base the base iterator type to reverse. Valid types are @ref +iterator (to create @ref reverse_iterator) and @ref const_iterator (to +create @ref const_reverse_iterator). + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +- [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + +@since version 1.0.0 +*/ +template +class json_reverse_iterator : public std::reverse_iterator +{ + public: + using difference_type = std::ptrdiff_t; + /// shortcut to the reverse iterator adapter + using base_iterator = std::reverse_iterator; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) {} + + /// create reverse iterator from base class + json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} + + /// post-increment (it++) + json_reverse_iterator const operator++(int) + { + return static_cast(base_iterator::operator++(1)); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + return static_cast(base_iterator::operator++()); + } + + /// post-decrement (it--) + json_reverse_iterator const operator--(int) + { + return static_cast(base_iterator::operator--(1)); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + return static_cast(base_iterator::operator--()); + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + return static_cast(base_iterator::operator+=(i)); + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + return static_cast(base_iterator::operator+(i)); + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + return static_cast(base_iterator::operator-(i)); + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return base_iterator(*this) - base_iterator(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + auto key() const -> decltype(std::declval().key()) + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } +}; +} +} + +// #include + + +#include // copy +#include // size_t +#include // streamsize +#include // back_inserter +#include // shared_ptr, make_shared +#include // basic_ostream +#include // basic_string +#include // vector + +namespace nlohmann +{ +namespace detail +{ +/// abstract output adapter interface +template struct output_adapter_protocol +{ + virtual void write_character(CharType c) = 0; + virtual void write_characters(const CharType* s, std::size_t length) = 0; + virtual ~output_adapter_protocol() = default; +}; + +/// a type to simplify interfaces +template +using output_adapter_t = std::shared_ptr>; + +/// output adapter for byte vectors +template +class output_vector_adapter : public output_adapter_protocol +{ + public: + explicit output_vector_adapter(std::vector& vec) : v(vec) {} + + void write_character(CharType c) override + { + v.push_back(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + std::copy(s, s + length, std::back_inserter(v)); + } + + private: + std::vector& v; +}; + +/// output adapter for output streams +template +class output_stream_adapter : public output_adapter_protocol +{ + public: + explicit output_stream_adapter(std::basic_ostream& s) : stream(s) {} + + void write_character(CharType c) override + { + stream.put(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + stream.write(s, static_cast(length)); + } + + private: + std::basic_ostream& stream; +}; + +/// output adapter for basic_string +template +class output_string_adapter : public output_adapter_protocol +{ + public: + explicit output_string_adapter(std::basic_string& s) : str(s) {} + + void write_character(CharType c) override + { + str.push_back(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + str.append(s, length); + } + + private: + std::basic_string& str; +}; + +template +class output_adapter +{ + public: + output_adapter(std::vector& vec) + : oa(std::make_shared>(vec)) {} + + output_adapter(std::basic_ostream& s) + : oa(std::make_shared>(s)) {} + + output_adapter(std::basic_string& s) + : oa(std::make_shared>(s)) {} + + operator output_adapter_t() + { + return oa; + } + + private: + output_adapter_t oa = nullptr; +}; +} +} + +// #include + + +#include // generate_n +#include // array +#include // assert +#include // ldexp +#include // size_t +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // setw, setfill +#include // hex +#include // back_inserter +#include // numeric_limits +#include // stringstream +#include // char_traits, string +#include // make_pair, move + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary reader // +/////////////////// + +/*! +@brief deserialization of CBOR and MessagePack values +*/ +template +class binary_reader +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using string_t = typename BasicJsonType::string_t; + + public: + /*! + @brief create a binary reader + + @param[in] adapter input adapter to read from + */ + explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter)) + { + assert(ia); + } + + /*! + @brief create a JSON value from CBOR input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from CBOR input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_cbor(const bool strict) + { + const auto res = parse_cbor_internal(); + if (strict) + { + get(); + expect_eof(); + } + return res; + } + + /*! + @brief create a JSON value from MessagePack input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from MessagePack input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_msgpack(const bool strict) + { + const auto res = parse_msgpack_internal(); + if (strict) + { + get(); + expect_eof(); + } + return res; + } + + /*! + @brief create a JSON value from UBJSON input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from UBJSON input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_ubjson(const bool strict) + { + const auto res = parse_ubjson_internal(); + if (strict) + { + get_ignore_noop(); + expect_eof(); + } + return res; + } + + /*! + @brief determine system byte order + + @return true if and only if system's byte order is little endian + + @note from http://stackoverflow.com/a/1001328/266378 + */ + static constexpr bool little_endianess(int num = 1) noexcept + { + return (*reinterpret_cast(&num) == 1); + } + + private: + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + BasicJsonType parse_cbor_internal(const bool get_char = true) + { + switch (get_char ? get() : current) + { + // EOF + case std::char_traits::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + return static_cast(current); + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + return get_number(); + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + return get_number(); + + case 0x1A: // Unsigned integer (four-byte uint32_t follows) + return get_number(); + + case 0x1B: // Unsigned integer (eight-byte uint64_t follows) + return get_number(); + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + return static_cast(0x20 - 1 - current); + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + return static_cast(-1) - get_number(); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + return static_cast(-1) - get_number(); + } + + case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) + { + return static_cast(-1) - get_number(); + } + + case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) + { + return static_cast(-1) - + static_cast(get_number()); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7F: // UTF-8 string (indefinite length) + { + return get_cbor_string(); + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + { + return get_cbor_array(current & 0x1F); + } + + case 0x98: // array (one-byte uint8_t for n follows) + { + return get_cbor_array(get_number()); + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + return get_cbor_array(get_number()); + } + + case 0x9A: // array (four-byte uint32_t for n follow) + { + return get_cbor_array(get_number()); + } + + case 0x9B: // array (eight-byte uint64_t for n follow) + { + return get_cbor_array(get_number()); + } + + case 0x9F: // array (indefinite length) + { + BasicJsonType result = value_t::array; + while (get() != 0xFF) + { + result.push_back(parse_cbor_internal(false)); + } + return result; + } + + // map (0x00..0x17 pairs of data items follow) + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + { + return get_cbor_object(current & 0x1F); + } + + case 0xB8: // map (one-byte uint8_t for n follows) + { + return get_cbor_object(get_number()); + } + + case 0xB9: // map (two-byte uint16_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBA: // map (four-byte uint32_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBB: // map (eight-byte uint64_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBF: // map (indefinite length) + { + BasicJsonType result = value_t::object; + while (get() != 0xFF) + { + auto key = get_cbor_string(); + result[key] = parse_cbor_internal(); + } + return result; + } + + case 0xF4: // false + { + return false; + } + + case 0xF5: // true + { + return true; + } + + case 0xF6: // null + { + return value_t::null; + } + + case 0xF9: // Half-Precision Float (two-byte IEEE 754) + { + const int byte1 = get(); + unexpect_eof(); + const int byte2 = get(); + unexpect_eof(); + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const int half = (byte1 << 8) + byte2; + const int exp = (half >> 10) & 0x1F; + const int mant = half & 0x3FF; + double val; + if (exp == 0) + { + val = std::ldexp(mant, -24); + } + else if (exp != 31) + { + val = std::ldexp(mant + 1024, exp - 25); + } + else + { + val = (mant == 0) ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + } + return (half & 0x8000) != 0 ? -val : val; + } + + case 0xFA: // Single-Precision Float (four-byte IEEE 754) + { + return get_number(); + } + + case 0xFB: // Double-Precision Float (eight-byte IEEE 754) + { + return get_number(); + } + + default: // anything else (0xFF is handled inside the other types) + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, "error reading CBOR; last byte: 0x" + ss.str())); + } + } + } + + BasicJsonType parse_msgpack_internal() + { + switch (get()) + { + // EOF + case std::char_traits::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + // positive fixint + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + return static_cast(current); + + // fixmap + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + { + return get_msgpack_object(current & 0x0F); + } + + // fixarray + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + case 0x98: + case 0x99: + case 0x9A: + case 0x9B: + case 0x9C: + case 0x9D: + case 0x9E: + case 0x9F: + { + return get_msgpack_array(current & 0x0F); + } + + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + return get_msgpack_string(); + + case 0xC0: // nil + return value_t::null; + + case 0xC2: // false + return false; + + case 0xC3: // true + return true; + + case 0xCA: // float 32 + return get_number(); + + case 0xCB: // float 64 + return get_number(); + + case 0xCC: // uint 8 + return get_number(); + + case 0xCD: // uint 16 + return get_number(); + + case 0xCE: // uint 32 + return get_number(); + + case 0xCF: // uint 64 + return get_number(); + + case 0xD0: // int 8 + return get_number(); + + case 0xD1: // int 16 + return get_number(); + + case 0xD2: // int 32 + return get_number(); + + case 0xD3: // int 64 + return get_number(); + + case 0xD9: // str 8 + case 0xDA: // str 16 + case 0xDB: // str 32 + return get_msgpack_string(); + + case 0xDC: // array 16 + { + return get_msgpack_array(get_number()); + } + + case 0xDD: // array 32 + { + return get_msgpack_array(get_number()); + } + + case 0xDE: // map 16 + { + return get_msgpack_object(get_number()); + } + + case 0xDF: // map 32 + { + return get_msgpack_object(get_number()); + } + + // positive fixint + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: + case 0xEF: + case 0xF0: + case 0xF1: + case 0xF2: + case 0xF3: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: + case 0xF8: + case 0xF9: + case 0xFA: + case 0xFB: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + return static_cast(current); + + default: // anything else + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading MessagePack; last byte: 0x" + ss.str())); + } + } + } + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + BasicJsonType parse_ubjson_internal(const bool get_char = true) + { + return get_ubjson_value(get_char ? get_ignore_noop() : current); + } + + /*! + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a -'ve valued + `std::char_traits::eof()` in that case. + + @return character read from the input + */ + int get() + { + ++chars_read; + return (current = ia->get_character()); + } + + /*! + @return character read from the input after ignoring all 'N' entries + */ + int get_ignore_noop() + { + do + { + get(); + } + while (current == 'N'); + + return current; + } + + /* + @brief read a number from the input + + @tparam NumberType the type of the number + + @return number of type @a NumberType + + @note This function needs to respect the system's endianess, because + bytes in CBOR and MessagePack are stored in network order (big + endian) and therefore need reordering on little endian systems. + + @throw parse_error.110 if input has less than `sizeof(NumberType)` bytes + */ + template NumberType get_number() + { + // step 1: read input into array with system's byte order + std::array vec; + for (std::size_t i = 0; i < sizeof(NumberType); ++i) + { + get(); + unexpect_eof(); + + // reverse byte order prior to conversion if necessary + if (is_little_endian) + { + vec[sizeof(NumberType) - i - 1] = static_cast(current); + } + else + { + vec[i] = static_cast(current); // LCOV_EXCL_LINE + } + } + + // step 2: convert array into number of type T and return + NumberType result; + std::memcpy(&result, vec.data(), sizeof(NumberType)); + return result; + } + + /*! + @brief create a string by reading characters from the input + + @param[in] len number of bytes to read + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of string memory. + + @return string created by reading @a len bytes + + @throw parse_error.110 if input has less than @a len bytes + */ + template + string_t get_string(const NumberType len) + { + string_t result; + std::generate_n(std::back_inserter(result), len, [this]() + { + get(); + unexpect_eof(); + return static_cast(current); + }); + return result; + } + + /*! + @brief reads a CBOR string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + Additionally, CBOR's strings with indefinite lengths are supported. + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + string_t get_cbor_string() + { + unexpect_eof(); + + switch (current) + { + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + return get_string(current & 0x1F); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + return get_string(get_number()); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + return get_string(get_number()); + } + + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + { + return get_string(get_number()); + } + + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + { + return get_string(get_number()); + } + + case 0x7F: // UTF-8 string (indefinite length) + { + string_t result; + while (get() != 0xFF) + { + result.append(get_cbor_string()); + } + return result; + } + + default: + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, "expected a CBOR string; last byte: 0x" + ss.str())); + } + } + } + + template + BasicJsonType get_cbor_array(const NumberType len) + { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_cbor_internal(); + }); + return result; + } + + template + BasicJsonType get_cbor_object(const NumberType len) + { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() + { + get(); + auto key = get_cbor_string(); + auto val = parse_cbor_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + + /*! + @brief reads a MessagePack string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + string_t get_msgpack_string() + { + unexpect_eof(); + + switch (current) + { + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + { + return get_string(current & 0x1F); + } + + case 0xD9: // str 8 + { + return get_string(get_number()); + } + + case 0xDA: // str 16 + { + return get_string(get_number()); + } + + case 0xDB: // str 32 + { + return get_string(get_number()); + } + + default: + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a MessagePack string; last byte: 0x" + ss.str())); + } + } + } + + template + BasicJsonType get_msgpack_array(const NumberType len) + { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_msgpack_internal(); + }); + return result; + } + + template + BasicJsonType get_msgpack_object(const NumberType len) + { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() + { + get(); + auto key = get_msgpack_string(); + auto val = parse_msgpack_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + + /*! + @brief reads a UBJSON string + + This function is either called after reading the 'S' byte explicitly + indicating a string, or in case of an object key where the 'S' byte can be + left out. + + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + string_t get_ubjson_string(const bool get_char = true) + { + if (get_char) + { + get(); // TODO: may we ignore N here? + } + + unexpect_eof(); + + switch (current) + { + case 'U': + return get_string(get_number()); + case 'i': + return get_string(get_number()); + case 'I': + return get_string(get_number()); + case 'l': + return get_string(get_number()); + case 'L': + return get_string(get_number()); + default: + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a UBJSON string; last byte: 0x" + ss.str())); + } + } + + /*! + @brief determine the type and size for a container + + In the optimized UBJSON format, a type and a size can be provided to allow + for a more compact representation. + + @return pair of the size and the type + */ + std::pair get_ubjson_size_type() + { + std::size_t sz = string_t::npos; + int tc = 0; + + get_ignore_noop(); + + if (current == '$') + { + tc = get(); // must not ignore 'N', because 'N' maybe the type + unexpect_eof(); + + get_ignore_noop(); + if (current != '#') + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "expected '#' after UBJSON type information; last byte: 0x" + ss.str())); + } + sz = parse_ubjson_internal(); + } + else if (current == '#') + { + sz = parse_ubjson_internal(); + } + + return std::make_pair(sz, tc); + } + + BasicJsonType get_ubjson_value(const int prefix) + { + switch (prefix) + { + case std::char_traits::eof(): // EOF + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + case 'T': // true + return true; + case 'F': // false + return false; + + case 'Z': // null + return nullptr; + + case 'U': + return get_number(); + case 'i': + return get_number(); + case 'I': + return get_number(); + case 'l': + return get_number(); + case 'L': + return get_number(); + case 'd': + return get_number(); + case 'D': + return get_number(); + + case 'C': // char + { + get(); + unexpect_eof(); + if (JSON_UNLIKELY(current > 127)) + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + ss.str())); + } + return string_t(1, static_cast(current)); + } + + case 'S': // string + return get_ubjson_string(); + + case '[': // array + return get_ubjson_array(); + + case '{': // object + return get_ubjson_object(); + + default: // anything else + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading UBJSON; last byte: 0x" + ss.str())); + } + } + + BasicJsonType get_ubjson_array() + { + BasicJsonType result = value_t::array; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != string_t::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive array size: " + std::to_string(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this, size_and_type]() + { + return get_ubjson_value(size_and_type.second); + }); + } + } + else + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this]() + { + return parse_ubjson_internal(); + }); + } + } + else + { + while (current != ']') + { + result.push_back(parse_ubjson_internal(false)); + get_ignore_noop(); + } + } + + return result; + } + + BasicJsonType get_ubjson_object() + { + BasicJsonType result = value_t::object; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != string_t::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive object size: " + std::to_string(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this, size_and_type]() + { + auto key = get_ubjson_string(); + auto val = get_ubjson_value(size_and_type.second); + return std::make_pair(std::move(key), std::move(val)); + }); + } + else + { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this]() + { + auto key = get_ubjson_string(); + auto val = parse_ubjson_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + } + } + else + { + while (current != '}') + { + auto key = get_ubjson_string(false); + result[std::move(key)] = parse_ubjson_internal(); + get_ignore_noop(); + } + } + + return result; + } + + /*! + @brief throw if end of input is not reached + @throw parse_error.110 if input not ended + */ + void expect_eof() const + { + if (JSON_UNLIKELY(current != std::char_traits::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "expected end of input")); + } + } + + /*! + @briefthrow if end of input is reached + @throw parse_error.110 if input ended + */ + void unexpect_eof() const + { + if (JSON_UNLIKELY(current == std::char_traits::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + } + } + + private: + /// input adapter + input_adapter_t ia = nullptr; + + /// the current character + int current = std::char_traits::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// whether we can assume little endianess + const bool is_little_endian = little_endianess(); +}; +} +} + +// #include + + +#include // reverse +#include // array +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // numeric_limits + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary writer // +/////////////////// + +/*! +@brief serialization to CBOR and MessagePack values +*/ +template +class binary_writer +{ + public: + /*! + @brief create a binary writer + + @param[in] adapter output adapter to write to + */ + explicit binary_writer(output_adapter_t adapter) : oa(adapter) + { + assert(oa); + } + + /*! + @brief[in] j JSON value to serialize + */ + void write_cbor(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: + { + oa->write_character(static_cast(0xF6)); + break; + } + + case value_t::boolean: + { + oa->write_character(j.m_value.boolean + ? static_cast(0xF5) + : static_cast(0xF4)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_value.number_integer <= 0x17) + { + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x18)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x19)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x1A)); + write_number(static_cast(j.m_value.number_integer)); + } + else + { + oa->write_character(static_cast(0x1B)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + // The conversions below encode the sign in the first + // byte, and the value is converted to a positive number. + const auto positive_number = -1 - j.m_value.number_integer; + if (j.m_value.number_integer >= -24) + { + write_number(static_cast(0x20 + positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x38)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x39)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x3A)); + write_number(static_cast(positive_number)); + } + else + { + oa->write_character(static_cast(0x3B)); + write_number(static_cast(positive_number)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= 0x17) + { + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x18)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x19)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x1A)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else + { + oa->write_character(static_cast(0x1B)); + write_number(static_cast(j.m_value.number_unsigned)); + } + break; + } + + case value_t::number_float: // Double-Precision Float + { + oa->write_character(static_cast(0xFB)); + write_number(j.m_value.number_float); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 0x17) + { + write_number(static_cast(0x60 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x78)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x79)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x7A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x7B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 0x17) + { + write_number(static_cast(0x80 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x98)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x99)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x9A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x9B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_cbor(el); + } + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 0x17) + { + write_number(static_cast(0xA0 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xB8)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xB9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xBA)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xBB)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_cbor(el.first); + write_cbor(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @brief[in] j JSON value to serialize + */ + void write_msgpack(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: // nil + { + oa->write_character(static_cast(0xC0)); + break; + } + + case value_t::boolean: // true and false + { + oa->write_character(j.m_value.boolean + ? static_cast(0xC3) + : static_cast(0xC2)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(static_cast(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(static_cast(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(static_cast(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(static_cast(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + if (j.m_value.number_integer >= -32) + { + // negative fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 8 + oa->write_character(static_cast(0xD0)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 16 + oa->write_character(static_cast(0xD1)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 32 + oa->write_character(static_cast(0xD2)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 64 + oa->write_character(static_cast(0xD3)); + write_number(static_cast(j.m_value.number_integer)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(static_cast(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(static_cast(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(static_cast(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(static_cast(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + break; + } + + case value_t::number_float: // float 64 + { + oa->write_character(static_cast(0xCB)); + write_number(j.m_value.number_float); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 31) + { + // fixstr + write_number(static_cast(0xA0 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 8 + oa->write_character(static_cast(0xD9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 16 + oa->write_character(static_cast(0xDA)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 32 + oa->write_character(static_cast(0xDB)); + write_number(static_cast(N)); + } + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 15) + { + // fixarray + write_number(static_cast(0x90 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 16 + oa->write_character(static_cast(0xDC)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 32 + oa->write_character(static_cast(0xDD)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_msgpack(el); + } + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 15) + { + // fixmap + write_number(static_cast(0x80 | (N & 0xF))); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 16 + oa->write_character(static_cast(0xDE)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 32 + oa->write_character(static_cast(0xDF)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_msgpack(el.first); + write_msgpack(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + @param[in] use_count whether to use '#' prefixes (optimized format) + @param[in] use_type whether to use '$' prefixes (optimized format) + @param[in] add_prefix whether prefixes need to be used for this value + */ + void write_ubjson(const BasicJsonType& j, const bool use_count, + const bool use_type, const bool add_prefix = true) + { + switch (j.type()) + { + case value_t::null: + { + if (add_prefix) + { + oa->write_character(static_cast('Z')); + } + break; + } + + case value_t::boolean: + { + if (add_prefix) + oa->write_character(j.m_value.boolean + ? static_cast('T') + : static_cast('F')); + break; + } + + case value_t::number_integer: + { + write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); + break; + } + + case value_t::number_unsigned: + { + write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); + break; + } + + case value_t::number_float: + { + write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); + break; + } + + case value_t::string: + { + if (add_prefix) + { + oa->write_character(static_cast('S')); + } + write_number_with_ubjson_prefix(j.m_value.string->size(), true); + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + if (add_prefix) + { + oa->write_character(static_cast('[')); + } + + bool prefix_required = true; + if (use_type and not j.m_value.array->empty()) + { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin() + 1, j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(static_cast('$')); + oa->write_character(static_cast(first_prefix)); + } + } + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.array->size(), true); + } + + for (const auto& el : *j.m_value.array) + { + write_ubjson(el, use_count, use_type, prefix_required); + } + + if (not use_count) + { + oa->write_character(static_cast(']')); + } + + break; + } + + case value_t::object: + { + if (add_prefix) + { + oa->write_character(static_cast('{')); + } + + bool prefix_required = true; + if (use_type and not j.m_value.object->empty()) + { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin(), j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(static_cast('$')); + oa->write_character(static_cast(first_prefix)); + } + } + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.object->size(), true); + } + + for (const auto& el : *j.m_value.object) + { + write_number_with_ubjson_prefix(el.first.size(), true); + oa->write_characters( + reinterpret_cast(el.first.c_str()), + el.first.size()); + write_ubjson(el.second, use_count, use_type, prefix_required); + } + + if (not use_count) + { + oa->write_character(static_cast('}')); + } + + break; + } + + default: + break; + } + } + + private: + /* + @brief write a number to output input + + @param[in] n number of type @a NumberType + @tparam NumberType the type of the number + + @note This function needs to respect the system's endianess, because bytes + in CBOR, MessagePack, and UBJSON are stored in network order (big + endian) and therefore need reordering on little endian systems. + */ + template + void write_number(const NumberType n) + { + // step 1: write number to array of length NumberType + std::array vec; + std::memcpy(vec.data(), &n, sizeof(NumberType)); + + // step 2: write array to output (with possible reordering) + if (is_little_endian) + { + // reverse byte order prior to conversion if necessary + std::reverse(vec.begin(), vec.end()); + } + + oa->write_characters(vec.data(), sizeof(NumberType)); + } + + template + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (std::is_floating_point::value) + { + if (add_prefix) + { + oa->write_character(static_cast('D')); // float64 + } + write_number(n); + } + else if (std::is_unsigned::value) + { + if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('i')); // int8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('U')); // uint8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('I')); // int16 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('l')); // int32 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('L')); // int64 + } + write_number(static_cast(n)); + } + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n))); + } + } + else + { + if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('i')); // int8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('U')); // uint8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('I')); // int16 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('l')); // int32 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('L')); // int64 + } + write_number(static_cast(n)); + } + // LCOV_EXCL_START + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n))); + } + // LCOV_EXCL_STOP + } + } + + /*! + @brief determine the type prefix of container values + + @note This function does not need to be 100% accurate when it comes to + integer limits. In case a number exceeds the limits of int64_t, + this will be detected by a later call to function + write_number_with_ubjson_prefix. Therefore, we return 'L' for any + value that does not fit the previous limits. + */ + char ubjson_prefix(const BasicJsonType& j) const noexcept + { + switch (j.type()) + { + case value_t::null: + return 'Z'; + + case value_t::boolean: + return j.m_value.boolean ? 'T' : 'F'; + + case value_t::number_integer: + { + if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_float: + return 'D'; + + case value_t::string: + return 'S'; + + case value_t::array: + return '['; + + case value_t::object: + return '{'; + + default: // discarded values + return 'N'; + } + } + + private: + /// whether we can assume little endianess + const bool is_little_endian = binary_reader::little_endianess(); + + /// the output + output_adapter_t oa = nullptr; +}; +} +} + +// #include + + +#include // reverse, remove, fill, find, none_of +#include // array +#include // assert +#include // and, or +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // setfill +#include // next +#include // numeric_limits +#include // string +#include // stringstream +#include // is_same + +// #include + +// #include + + +#include // assert +#include // or, and, not +#include // signbit, isfinite +#include // intN_t, uintN_t +#include // memcpy, memmove + +namespace nlohmann +{ +namespace detail +{ + +/*! +@brief implements the Grisu2 algorithm for binary to decimal floating-point +conversion. + +This implementation is a slightly modified version of the reference +implementation which may be obtained from +http://florian.loitsch.com/publications (bench.tar.gz). + +The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. + +For a detailed description of the algorithm see: + +[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with + Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming + Language Design and Implementation, PLDI 2010 +[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", + Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language + Design and Implementation, PLDI 1996 +*/ +namespace dtoa_impl +{ + +template +Target reinterpret_bits(const Source source) +{ + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); + + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; +} + +struct diyfp // f * 2^e +{ + static constexpr int kPrecision = 64; // = q + + uint64_t f; + int e; + + constexpr diyfp() noexcept : f(0), e(0) {} + constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + /*! + @brief returns x - y + @pre x.e == y.e and x.f >= y.f + */ + static diyfp sub(const diyfp& x, const diyfp& y) noexcept + { + assert(x.e == y.e); + assert(x.f >= y.f); + + return diyfp(x.f - y.f, x.e); + } + + /*! + @brief returns x * y + @note The result is rounded. (Only the upper q bits are returned.) + */ + static diyfp mul(const diyfp& x, const diyfp& y) noexcept + { + static_assert(kPrecision == 64, "internal error"); + + // Computes: + // f = round((x.f * y.f) / 2^q) + // e = x.e + y.e + q + + // Emulate the 64-bit * 64-bit multiplication: + // + // p = u * v + // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) + // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) + // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) + // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) + // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) + // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) + // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) + // + // (Since Q might be larger than 2^32 - 1) + // + // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) + // + // (Q_hi + H does not overflow a 64-bit int) + // + // = p_lo + 2^64 p_hi + + const uint64_t u_lo = x.f & 0xFFFFFFFF; + const uint64_t u_hi = x.f >> 32; + const uint64_t v_lo = y.f & 0xFFFFFFFF; + const uint64_t v_hi = y.f >> 32; + + const uint64_t p0 = u_lo * v_lo; + const uint64_t p1 = u_lo * v_hi; + const uint64_t p2 = u_hi * v_lo; + const uint64_t p3 = u_hi * v_hi; + + const uint64_t p0_hi = p0 >> 32; + const uint64_t p1_lo = p1 & 0xFFFFFFFF; + const uint64_t p1_hi = p1 >> 32; + const uint64_t p2_lo = p2 & 0xFFFFFFFF; + const uint64_t p2_hi = p2 >> 32; + + uint64_t Q = p0_hi + p1_lo + p2_lo; + + // The full product might now be computed as + // + // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) + // p_lo = p0_lo + (Q << 32) + // + // But in this particular case here, the full p_lo is not required. + // Effectively we only need to add the highest bit in p_lo to p_hi (and + // Q_hi + 1 does not overflow). + + Q += uint64_t{1} << (64 - 32 - 1); // round, ties up + + const uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32); + + return diyfp(h, x.e + y.e + 64); + } + + /*! + @brief normalize x such that the significand is >= 2^(q-1) + @pre x.f != 0 + */ + static diyfp normalize(diyfp x) noexcept + { + assert(x.f != 0); + + while ((x.f >> 63) == 0) + { + x.f <<= 1; + x.e--; + } + + return x; + } + + /*! + @brief normalize x such that the result has the exponent E + @pre e >= x.e and the upper e - x.e bits of x.f must be zero. + */ + static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept + { + const int delta = x.e - target_exponent; + + assert(delta >= 0); + assert(((x.f << delta) >> delta) == x.f); + + return diyfp(x.f << delta, target_exponent); + } +}; + +struct boundaries +{ + diyfp w; + diyfp minus; + diyfp plus; +}; + +/*! +Compute the (normalized) diyfp representing the input number 'value' and its +boundaries. + +@pre value must be finite and positive +*/ +template +boundaries compute_boundaries(FloatType value) +{ + assert(std::isfinite(value)); + assert(value > 0); + + // Convert the IEEE representation into a diyfp. + // + // If v is denormal: + // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) + // If v is normalized: + // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) + + static_assert(std::numeric_limits::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + + constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) + constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); + constexpr int kMinExp = 1 - kBias; + constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1) + + using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type; + + const uint64_t bits = reinterpret_bits(value); + const uint64_t E = bits >> (kPrecision - 1); + const uint64_t F = bits & (kHiddenBit - 1); + + const bool is_denormal = (E == 0); + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F + kHiddenBit, static_cast(E) - kBias); + + // Compute the boundaries m- and m+ of the floating-point value + // v = f * 2^e. + // + // Determine v- and v+, the floating-point predecessor and successor if v, + // respectively. + // + // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) + // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) + // + // v+ = v + 2^e + // + // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ + // between m- and m+ round to v, regardless of how the input rounding + // algorithm breaks ties. + // + // ---+-------------+-------------+-------------+-------------+--- (A) + // v- m- v m+ v+ + // + // -----------------+------+------+-------------+-------------+--- (B) + // v- m- v m+ v+ + + const bool lower_boundary_is_closer = (F == 0 and E > 1); + const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp(4 * v.f - 1, v.e - 2) // (B) + : diyfp(2 * v.f - 1, v.e - 1); // (A) + + // Determine the normalized w+ = m+. + const diyfp w_plus = diyfp::normalize(m_plus); + + // Determine w- = m- such that e_(w-) = e_(w+). + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// Given normalized diyfp w, Grisu needs to find a (normalized) cached +// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies +// within a certain range [alpha, gamma] (Definition 3.2 from [1]) +// +// alpha <= e = e_c + e_w + q <= gamma +// +// or +// +// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q +// <= f_c * f_w * 2^gamma +// +// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies +// +// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma +// +// or +// +// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) +// +// The choice of (alpha,gamma) determines the size of the table and the form of +// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well +// in practice: +// +// The idea is to cut the number c * w = f * 2^e into two parts, which can be +// processed independently: An integral part p1, and a fractional part p2: +// +// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e +// = (f div 2^-e) + (f mod 2^-e) * 2^e +// = p1 + p2 * 2^e +// +// The conversion of p1 into decimal form requires a series of divisions and +// modulos by (a power of) 10. These operations are faster for 32-bit than for +// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be +// achieved by choosing +// +// -e >= 32 or e <= -32 := gamma +// +// In order to convert the fractional part +// +// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... +// +// into decimal form, the fraction is repeatedly multiplied by 10 and the digits +// d[-i] are extracted in order: +// +// (10 * p2) div 2^-e = d[-1] +// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... +// +// The multiplication by 10 must not overflow. It is sufficient to choose +// +// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. +// +// Since p2 = f mod 2^-e < 2^-e, +// +// -e <= 60 or e >= -60 := alpha + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power // c = f * 2^e ~= 10^k +{ + uint64_t f; + int e; + int k; +}; + +/*! +For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached +power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c +satisfies (Definition 3.2 from [1]) + + alpha <= e_c + e + q <= gamma. +*/ +inline cached_power get_cached_power_for_binary_exponent(int e) +{ + // Now + // + // alpha <= e_c + e + q <= gamma (1) + // ==> f_c * 2^alpha <= c * 2^e * 2^q + // + // and since the c's are normalized, 2^(q-1) <= f_c, + // + // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) + // ==> 2^(alpha - e - 1) <= c + // + // If c were an exakt power of ten, i.e. c = 10^k, one may determine k as + // + // k = ceil( log_10( 2^(alpha - e - 1) ) ) + // = ceil( (alpha - e - 1) * log_10(2) ) + // + // From the paper: + // "In theory the result of the procedure could be wrong since c is rounded, + // and the computation itself is approximated [...]. In practice, however, + // this simple function is sufficient." + // + // For IEEE double precision floating-point numbers converted into + // normalized diyfp's w = f * 2^e, with q = 64, + // + // e >= -1022 (min IEEE exponent) + // -52 (p - 1) + // -52 (p - 1, possibly normalize denormal IEEE numbers) + // -11 (normalize the diyfp) + // = -1137 + // + // and + // + // e <= +1023 (max IEEE exponent) + // -52 (p - 1) + // -11 (normalize the diyfp) + // = 960 + // + // This binary exponent range [-1137,960] results in a decimal exponent + // range [-307,324]. One does not need to store a cached power for each + // k in this range. For each such k it suffices to find a cached power + // such that the exponent of the product lies in [alpha,gamma]. + // This implies that the difference of the decimal exponents of adjacent + // table entries must be less than or equal to + // + // floor( (gamma - alpha) * log_10(2) ) = 8. + // + // (A smaller distance gamma-alpha would require a larger table.) + + // NB: + // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. + + constexpr int kCachedPowersSize = 79; + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr cached_power kCachedPowers[] = + { + { 0xAB70FE17C79AC6CA, -1060, -300 }, + { 0xFF77B1FCBEBCDC4F, -1034, -292 }, + { 0xBE5691EF416BD60C, -1007, -284 }, + { 0x8DD01FAD907FFC3C, -980, -276 }, + { 0xD3515C2831559A83, -954, -268 }, + { 0x9D71AC8FADA6C9B5, -927, -260 }, + { 0xEA9C227723EE8BCB, -901, -252 }, + { 0xAECC49914078536D, -874, -244 }, + { 0x823C12795DB6CE57, -847, -236 }, + { 0xC21094364DFB5637, -821, -228 }, + { 0x9096EA6F3848984F, -794, -220 }, + { 0xD77485CB25823AC7, -768, -212 }, + { 0xA086CFCD97BF97F4, -741, -204 }, + { 0xEF340A98172AACE5, -715, -196 }, + { 0xB23867FB2A35B28E, -688, -188 }, + { 0x84C8D4DFD2C63F3B, -661, -180 }, + { 0xC5DD44271AD3CDBA, -635, -172 }, + { 0x936B9FCEBB25C996, -608, -164 }, + { 0xDBAC6C247D62A584, -582, -156 }, + { 0xA3AB66580D5FDAF6, -555, -148 }, + { 0xF3E2F893DEC3F126, -529, -140 }, + { 0xB5B5ADA8AAFF80B8, -502, -132 }, + { 0x87625F056C7C4A8B, -475, -124 }, + { 0xC9BCFF6034C13053, -449, -116 }, + { 0x964E858C91BA2655, -422, -108 }, + { 0xDFF9772470297EBD, -396, -100 }, + { 0xA6DFBD9FB8E5B88F, -369, -92 }, + { 0xF8A95FCF88747D94, -343, -84 }, + { 0xB94470938FA89BCF, -316, -76 }, + { 0x8A08F0F8BF0F156B, -289, -68 }, + { 0xCDB02555653131B6, -263, -60 }, + { 0x993FE2C6D07B7FAC, -236, -52 }, + { 0xE45C10C42A2B3B06, -210, -44 }, + { 0xAA242499697392D3, -183, -36 }, + { 0xFD87B5F28300CA0E, -157, -28 }, + { 0xBCE5086492111AEB, -130, -20 }, + { 0x8CBCCC096F5088CC, -103, -12 }, + { 0xD1B71758E219652C, -77, -4 }, + { 0x9C40000000000000, -50, 4 }, + { 0xE8D4A51000000000, -24, 12 }, + { 0xAD78EBC5AC620000, 3, 20 }, + { 0x813F3978F8940984, 30, 28 }, + { 0xC097CE7BC90715B3, 56, 36 }, + { 0x8F7E32CE7BEA5C70, 83, 44 }, + { 0xD5D238A4ABE98068, 109, 52 }, + { 0x9F4F2726179A2245, 136, 60 }, + { 0xED63A231D4C4FB27, 162, 68 }, + { 0xB0DE65388CC8ADA8, 189, 76 }, + { 0x83C7088E1AAB65DB, 216, 84 }, + { 0xC45D1DF942711D9A, 242, 92 }, + { 0x924D692CA61BE758, 269, 100 }, + { 0xDA01EE641A708DEA, 295, 108 }, + { 0xA26DA3999AEF774A, 322, 116 }, + { 0xF209787BB47D6B85, 348, 124 }, + { 0xB454E4A179DD1877, 375, 132 }, + { 0x865B86925B9BC5C2, 402, 140 }, + { 0xC83553C5C8965D3D, 428, 148 }, + { 0x952AB45CFA97A0B3, 455, 156 }, + { 0xDE469FBD99A05FE3, 481, 164 }, + { 0xA59BC234DB398C25, 508, 172 }, + { 0xF6C69A72A3989F5C, 534, 180 }, + { 0xB7DCBF5354E9BECE, 561, 188 }, + { 0x88FCF317F22241E2, 588, 196 }, + { 0xCC20CE9BD35C78A5, 614, 204 }, + { 0x98165AF37B2153DF, 641, 212 }, + { 0xE2A0B5DC971F303A, 667, 220 }, + { 0xA8D9D1535CE3B396, 694, 228 }, + { 0xFB9B7CD9A4A7443C, 720, 236 }, + { 0xBB764C4CA7A44410, 747, 244 }, + { 0x8BAB8EEFB6409C1A, 774, 252 }, + { 0xD01FEF10A657842C, 800, 260 }, + { 0x9B10A4E5E9913129, 827, 268 }, + { 0xE7109BFBA19C0C9D, 853, 276 }, + { 0xAC2820D9623BF429, 880, 284 }, + { 0x80444B5E7AA7CF85, 907, 292 }, + { 0xBF21E44003ACDD2D, 933, 300 }, + { 0x8E679C2F5E44FF8F, 960, 308 }, + { 0xD433179D9C8CB841, 986, 316 }, + { 0x9E19DB92B4E31BA9, 1013, 324 }, + }; + + // This computation gives exactly the same results for k as + // k = ceil((kAlpha - e - 1) * 0.30102999566398114) + // for |e| <= 1500, but doesn't require floating-point operations. + // NB: log_10(2) ~= 78913 / 2^18 + assert(e >= -1500); + assert(e <= 1500); + const int f = kAlpha - e - 1; + const int k = (f * 78913) / (1 << 18) + (f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; + assert(index >= 0); + assert(index < kCachedPowersSize); + static_cast(kCachedPowersSize); // Fix warning. + + const cached_power cached = kCachedPowers[index]; + assert(kAlpha <= cached.e + e + 64); + assert(kGamma >= cached.e + e + 64); + + return cached; +} + +/*! +For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. +For n == 0, returns 1 and sets pow10 := 1. +*/ +inline int find_largest_pow10(const uint32_t n, uint32_t& pow10) +{ + // LCOV_EXCL_START + if (n >= 1000000000) + { + pow10 = 1000000000; + return 10; + } + // LCOV_EXCL_STOP + else if (n >= 100000000) + { + pow10 = 100000000; + return 9; + } + else if (n >= 10000000) + { + pow10 = 10000000; + return 8; + } + else if (n >= 1000000) + { + pow10 = 1000000; + return 7; + } + else if (n >= 100000) + { + pow10 = 100000; + return 6; + } + else if (n >= 10000) + { + pow10 = 10000; + return 5; + } + else if (n >= 1000) + { + pow10 = 1000; + return 4; + } + else if (n >= 100) + { + pow10 = 100; + return 3; + } + else if (n >= 10) + { + pow10 = 10; + return 2; + } + else + { + pow10 = 1; + return 1; + } +} + +inline void grisu2_round(char* buf, int len, uint64_t dist, uint64_t delta, + uint64_t rest, uint64_t ten_k) +{ + assert(len >= 1); + assert(dist <= delta); + assert(rest <= delta); + assert(ten_k > 0); + + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // ten_k + // <------> + // <---- rest ----> + // --------------[------------------+----+--------------]-------------- + // w V + // = buf * 10^k + // + // ten_k represents a unit-in-the-last-place in the decimal representation + // stored in buf. + // Decrement buf by ten_k while this takes buf closer to w. + + // The tests are written in this order to avoid overflow in unsigned + // integer arithmetic. + + while (rest < dist + and delta - rest >= ten_k + and (rest + ten_k < dist or dist - rest > rest + ten_k - dist)) + { + assert(buf[len - 1] != '0'); + buf[len - 1]--; + rest += ten_k; + } +} + +/*! +Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. +M- and M+ must be normalized and share the same exponent -60 <= e <= -32. +*/ +inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) +{ + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); + + // Generates the digits (and the exponent) of a decimal floating-point + // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's + // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. + // + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // Grisu2 generates the digits of M+ from left to right and stops as soon as + // V is in [M-,M+]. + + assert(M_plus.e >= kAlpha); + assert(M_plus.e <= kGamma); + + uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) + uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) + + // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): + // + // M+ = f * 2^e + // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e + // = ((p1 ) * 2^-e + (p2 )) * 2^e + // = p1 + p2 * 2^e + + const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e); + + uint32_t p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) + uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e + + // 1) + // + // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] + + assert(p1 > 0); + + uint32_t pow10; + const int k = find_largest_pow10(p1, pow10); + + // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) + // + // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) + // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) + // + // M+ = p1 + p2 * 2^e + // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e + // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e + // = d[k-1] * 10^(k-1) + ( rest) * 2^e + // + // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) + // + // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] + // + // but stop as soon as + // + // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e + + int n = k; + while (n > 0) + { + // Invariants: + // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) + // pow10 = 10^(n-1) <= p1 < 10^n + // + const uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) + const uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) + // + // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e + // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) + // + p1 = r; + n--; + // + // M+ = buffer * 10^n + (p1 + p2 * 2^e) + // pow10 = 10^n + // + + // Now check if enough digits have been generated. + // Compute + // + // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e + // + // Note: + // Since rest and delta share the same exponent e, it suffices to + // compare the significands. + const uint64_t rest = (uint64_t{p1} << -one.e) + p2; + if (rest <= delta) + { + // V = buffer * 10^n, with M- <= V <= M+. + + decimal_exponent += n; + + // We may now just stop. But instead look if the buffer could be + // decremented to bring V closer to w. + // + // pow10 = 10^n is now 1 ulp in the decimal representation V. + // The rounding procedure works with diyfp's with an implicit + // exponent of e. + // + // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e + // + const uint64_t ten_n = uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + // pow10 = 10^(n-1) <= p1 < 10^n + // Invariants restored. + } + + // 2) + // + // The digits of the integral part have been generated: + // + // M+ = d[k-1]...d[1]d[0] + p2 * 2^e + // = buffer + p2 * 2^e + // + // Now generate the digits of the fractional part p2 * 2^e. + // + // Note: + // No decimal point is generated: the exponent is adjusted instead. + // + // p2 actually represents the fraction + // + // p2 * 2^e + // = p2 / 2^-e + // = d[-1] / 10^1 + d[-2] / 10^2 + ... + // + // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) + // + // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m + // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) + // + // using + // + // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) + // = ( d) * 2^-e + ( r) + // + // or + // 10^m * p2 * 2^e = d + r * 2^e + // + // i.e. + // + // M+ = buffer + p2 * 2^e + // = buffer + 10^-m * (d + r * 2^e) + // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e + // + // and stop as soon as 10^-m * r * 2^e <= delta * 2^e + + assert(p2 > delta); + + int m = 0; + for (;;) + { + // Invariant: + // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e + // = buffer * 10^-m + 10^-m * (p2 ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e + // + assert(p2 <= UINT64_MAX / 10); + p2 *= 10; + const uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e + const uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e + // + // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) + // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + p2 = r; + m++; + // + // M+ = buffer * 10^-m + 10^-m * p2 * 2^e + // Invariant restored. + + // Check if enough digits have been generated. + // + // 10^-m * p2 * 2^e <= delta * 2^e + // p2 * 2^e <= 10^m * delta * 2^e + // p2 <= 10^m * delta + delta *= 10; + dist *= 10; + if (p2 <= delta) + { + break; + } + } + + // V = buffer * 10^-m, with M- <= V <= M+. + + decimal_exponent -= m; + + // 1 ulp in the decimal representation is now 10^-m. + // Since delta and dist are now scaled by 10^m, we need to do the + // same with ulp in order to keep the units in sync. + // + // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e + // + const uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // By construction this algorithm generates the shortest possible decimal + // number (Loitsch, Theorem 6.2) which rounds back to w. + // For an input number of precision p, at least + // + // N = 1 + ceil(p * log_10(2)) + // + // decimal digits are sufficient to identify all binary floating-point + // numbers (Matula, "In-and-Out conversions"). + // This implies that the algorithm does not produce more than N decimal + // digits. + // + // N = 17 for p = 53 (IEEE double precision) + // N = 9 for p = 24 (IEEE single precision) +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +inline void grisu2(char* buf, int& len, int& decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) +{ + assert(m_plus.e == m_minus.e); + assert(m_plus.e == v.e); + + // --------(-----------------------+-----------------------)-------- (A) + // m- v m+ + // + // --------------------(-----------+-----------------------)-------- (B) + // m- v m+ + // + // First scale v (and m- and m+) such that the exponent is in the range + // [alpha, gamma]. + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k + + // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + + // ----(---+---)---------------(---+---)---------------(---+---)---- + // w- w w+ + // = c*m- = c*v = c*m+ + // + // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and + // w+ are now off by a small amount. + // In fact: + // + // w - v * 10^k < 1 ulp + // + // To account for this inaccuracy, add resp. subtract 1 ulp. + // + // --------+---[---------------(---+---)---------------]---+-------- + // w- M- w M+ w+ + // + // Now any number in [M-, M+] (bounds included) will round to w when input, + // regardless of how the input rounding algorithm breaks ties. + // + // And digit_gen generates the shortest possible such number in [M-, M+]. + // Note that this does not mean that Grisu2 always generates the shortest + // possible number in the interval (m-, m+). + const diyfp M_minus(w_minus.f + 1, w_minus.e); + const diyfp M_plus (w_plus.f - 1, w_plus.e ); + + decimal_exponent = -cached.k; // = -(-k) = k + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +template +void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) +{ + static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, + "internal error: not enough precision"); + + assert(std::isfinite(value)); + assert(value > 0); + + // If the neighbors (and boundaries) of 'value' are always computed for double-precision + // numbers, all float's can be recovered using strtod (and strtof). However, the resulting + // decimal representations are not exactly "short". + // + // The documentation for 'std::to_chars' (http://en.cppreference.com/w/cpp/utility/to_chars) + // says "value is converted to a string as if by std::sprintf in the default ("C") locale" + // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' + // does. + // On the other hand, the documentation for 'std::to_chars' requires that "parsing the + // representation using the corresponding std::from_chars function recovers value exactly". That + // indicates that single precision floating-point numbers should be recovered using + // 'std::strtof'. + // + // NB: If the neighbors are computed for single-precision numbers, there is a single float + // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision + // value is off by 1 ulp. +#if 0 + const boundaries w = compute_boundaries(static_cast(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +/*! +@brief appends a decimal representation of e to buf +@return a pointer to the element following the exponent. +@pre -1000 < e < 1000 +*/ +inline char* append_exponent(char* buf, int e) +{ + assert(e > -1000); + assert(e < 1000); + + if (e < 0) + { + e = -e; + *buf++ = '-'; + } + else + { + *buf++ = '+'; + } + + uint32_t k = static_cast(e); + if (k < 10) + { + // Always print at least two digits in the exponent. + // This is for compatibility with printf("%g"). + *buf++ = '0'; + *buf++ = static_cast('0' + k); + } + else if (k < 100) + { + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + else + { + *buf++ = static_cast('0' + k / 100); + k %= 100; + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + + return buf; +} + +/*! +@brief prettify v = buf * 10^decimal_exponent + +If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point +notation. Otherwise it will be printed in exponential notation. + +@pre min_exp < 0 +@pre max_exp > 0 +*/ +inline char* format_buffer(char* buf, int len, int decimal_exponent, + int min_exp, int max_exp) +{ + assert(min_exp < 0); + assert(max_exp > 0); + + const int k = len; + const int n = len + decimal_exponent; + + // v = buf * 10^(n-k) + // k is the length of the buffer (number of decimal digits) + // n is the position of the decimal point relative to the start of the buffer. + + if (k <= n and n <= max_exp) + { + // digits[000] + // len <= max_exp + 2 + + std::memset(buf + k, '0', static_cast(n - k)); + // Make it look like a floating-point number (#362, #378) + buf[n + 0] = '.'; + buf[n + 1] = '0'; + return buf + (n + 2); + } + + if (0 < n and n <= max_exp) + { + // dig.its + // len <= max_digits10 + 1 + + assert(k > n); + + std::memmove(buf + (n + 1), buf + n, static_cast(k - n)); + buf[n] = '.'; + return buf + (k + 1); + } + + if (min_exp < n and n <= 0) + { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + -n), buf, static_cast(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast(-n)); + return buf + (2 + (-n) + k); + } + + if (k == 1) + { + // dE+123 + // len <= 1 + 5 + + buf += 1; + } + else + { + // d.igitsE+123 + // len <= max_digits10 + 1 + 5 + + std::memmove(buf + 2, buf + 1, static_cast(k - 1)); + buf[1] = '.'; + buf += 1 + k; + } + + *buf++ = 'e'; + return append_exponent(buf, n - 1); +} + +} // namespace dtoa_impl + +/*! +@brief generates a decimal representation of the floating-point number value in [first, last). + +The format of the resulting decimal representation is similar to printf's %g +format. Returns an iterator pointing past-the-end of the decimal representation. + +@note The input number must be finite, i.e. NaN's and Inf's are not supported. +@note The buffer must be large enough. +@note The result is NOT null-terminated. +*/ +template +char* to_chars(char* first, char* last, FloatType value) +{ + static_cast(last); // maybe unused - fix warning + assert(std::isfinite(value)); + + // Use signbit(value) instead of (value < 0) since signbit works for -0. + if (std::signbit(value)) + { + value = -value; + *first++ = '-'; + } + + if (value == 0) // +-0 + { + *first++ = '0'; + // Make it look like a floating-point number (#362, #378) + *first++ = '.'; + *first++ = '0'; + return first; + } + + assert(last - first >= std::numeric_limits::max_digits10); + + // Compute v = buffer * 10^decimal_exponent. + // The decimal digits are stored in the buffer, which needs to be interpreted + // as an unsigned decimal integer. + // len is the length of the buffer, i.e. the number of decimal digits. + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + + assert(len <= std::numeric_limits::max_digits10); + + // Format the buffer like printf("%.*g", prec, value) + constexpr int kMinExp = -4; + // Use digits10 here to increase compatibility with version 2. + constexpr int kMaxExp = std::numeric_limits::digits10; + + assert(last - first >= kMaxExp + 2); + assert(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); + assert(last - first >= std::numeric_limits::max_digits10 + 6); + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} + +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// serialization // +/////////////////// + +template +class serializer +{ + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + */ + serializer(output_adapter_t s, const char ichar) + : o(std::move(s)), loc(std::localeconv()), + thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), + decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)), + indent_char(ichar), indent_string(512, indent_char) + {} + + // delete because of pointer members + serializer(const serializer&) = delete; + serializer& operator=(const serializer&) = delete; + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(const BasicJsonType& val, const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) + { + switch (val.m_type) + { + case value_t::object: + { + if (val.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + + o->write_character('}'); + } + + return; + } + + case value_t::array: + { + if (val.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (pretty_print) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + + o->write_character(']'); + } + + return; + } + + case value_t::string: + { + o->write_character('\"'); + dump_escaped(*val.m_value.string, ensure_ascii); + o->write_character('\"'); + return; + } + + case value_t::boolean: + { + if (val.m_value.boolean) + { + o->write_characters("true", 4); + } + else + { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: + { + dump_integer(val.m_value.number_integer); + return; + } + + case value_t::number_unsigned: + { + dump_integer(val.m_value.number_unsigned); + return; + } + + case value_t::number_float: + { + dump_float(val.m_value.number_float); + return; + } + + case value_t::discarded: + { + o->write_characters("", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + } + } + + private: + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(const string_t& s, const bool ensure_ascii) + { + uint32_t codepoint; + uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer + + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) + { + case UTF8_ACCEPT: // decode found a new code point + { + switch (codepoint) + { + case 0x08: // backspace + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } + + case 0x09: // horizontal tab + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast(codepoint)); + bytes += 6; + } + else + { + std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast(0xD7C0 + (codepoint >> 10)), + static_cast(0xDC00 + (codepoint & 0x3FF))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + ss.str())); + } + + default: // decode found yet incomplete multi-byte code point + { + if (not ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; + } + break; + } + } + } + + if (JSON_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o->write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(static_cast(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + ss.str())); + } + } + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template::value or + std::is_same::value, + int> = 0> + void dump_integer(NumberType x) + { + // special case for "0" + if (x == 0) + { + o->write_character('0'); + return; + } + + const bool is_negative = (x <= 0) and (x != 0); // see issue #755 + std::size_t i = 0; + + while (x != 0) + { + // spare 1 byte for '\0' + assert(i < number_buffer.size() - 1); + + const auto digit = std::labs(static_cast(x % 10)); + number_buffer[i++] = static_cast('0' + digit); + x /= 10; + } + + if (is_negative) + { + // make sure there is capacity for the '-' + assert(i < number_buffer.size() - 2); + number_buffer[i++] = '-'; + } + + std::reverse(number_buffer.begin(), number_buffer.begin() + i); + o->write_characters(number_buffer.data(), i); + } + + /*! + @brief dump a floating-point number + + Dump a given floating-point number to output stream @a o. Works internally + with @a number_buffer. + + @param[in] x floating-point number to dump + */ + void dump_float(number_float_t x) + { + // NaN / inf + if (not std::isfinite(x)) + { + o->write_characters("null", 4); + return; + } + + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if == . + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 24 and std::numeric_limits::max_exponent == 128) or + (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 53 and std::numeric_limits::max_exponent == 1024); + + dump_float(x, std::integral_constant()); + } + + void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + char* begin = number_buffer.data(); + char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o->write_characters(begin, static_cast(end - begin)); + } + + void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits::max_digits10; + + // the actual conversion + std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + // negative value indicates an error + assert(len > 0); + // check if buffer was large enough + assert(static_cast(len) < number_buffer.size()); + + // erase thousands separator + if (thousands_sep != '\0') + { + const auto end = std::remove(number_buffer.begin(), + number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + assert((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } + + // convert decimal point to '.' + if (decimal_point != '\0' and decimal_point != '.') + { + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if (dec_pos != number_buffer.end()) + { + *dec_pos = '.'; + } + } + + o->write_characters(number_buffer.data(), static_cast(len)); + + // determine if need to append ".0" + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) + { + return (c == '.' or c == 'e'); + }); + + if (value_is_int_like) + { + o->write_characters(".0", 2); + } + } + + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept + { + static const std::array utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 + } + }; + + const uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6) + : static_cast(0xff >> type) & (byte); + + state = utf8d[256u + state * 16u + type]; + return state; + } + + private: + /// the output of the serializer + output_adapter_t o = nullptr; + + /// a (hopefully) large enough character buffer + std::array number_buffer{{}}; + + /// the locale + const std::lconv* loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; + + /// string buffer + std::array string_buffer{{}}; + + /// the indentation character + const char indent_char; + /// the indentation string + string_t indent_string; +}; +} +} + +// #include + + +#include +#include + +namespace nlohmann +{ +namespace detail +{ +template +class json_ref +{ + public: + using value_type = BasicJsonType; + + json_ref(value_type&& value) + : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true) + {} + + json_ref(const value_type& value) + : value_ref(const_cast(&value)), is_rvalue(false) + {} + + json_ref(std::initializer_list init) + : owned_value(init), value_ref(&owned_value), is_rvalue(true) + {} + + template + json_ref(Args&& ... args) + : owned_value(std::forward(args)...), value_ref(&owned_value), is_rvalue(true) + {} + + // class should be movable only + json_ref(json_ref&&) = default; + json_ref(const json_ref&) = delete; + json_ref& operator=(const json_ref&) = delete; + + value_type moved_or_copied() const + { + if (is_rvalue) + { + return std::move(*value_ref); + } + return *value_ref; + } + + value_type const& operator*() const + { + return *static_cast(value_ref); + } + + value_type const* operator->() const + { + return static_cast(value_ref); + } + + private: + mutable value_type owned_value = nullptr; + value_type* value_ref = nullptr; + const bool is_rvalue; +}; +} +} + +// #include + + +#include // assert +#include // accumulate +#include // string +#include // vector + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +template +class json_pointer +{ + // allow basic_json to access private members + NLOHMANN_BASIC_JSON_TPL_DECLARATION + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the empty + string is assumed which references the whole JSON value + + @throw parse_error.107 if the given JSON pointer @a s is nonempty and does + not begin with a slash (`/`); see example below + + @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is + not followed by `0` (representing `~`) or `1` (representing `/`); see + example below + + @liveexample{The example shows the construction several valid JSON pointers + as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`., + json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const noexcept + { + return std::accumulate(reference_tokens.begin(), reference_tokens.end(), + std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + escape(b); + }); + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + /*! + @param[in] s reference token to be converted into an array index + + @return integer representation of @a s + + @throw out_of_range.404 if string @a s could not be converted to an integer + */ + static int array_index(const std::string& s) + { + std::size_t processed_chars = 0; + const int res = std::stoi(s, &processed_chars); + + // check if the string was completely read + if (JSON_UNLIKELY(processed_chars != s.size())) + { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); + } + + return res; + } + + private: + /*! + @brief remove and return last reference pointer + @throw out_of_range.405 if JSON pointer has no parent + */ + std::string pop_back() + { + if (JSON_UNLIKELY(is_root())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; + } + + /// return whether pointer points to the root document + bool is_root() const + { + return reference_tokens.empty(); + } + + json_pointer top() const + { + if (JSON_UNLIKELY(is_root())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + + @throw parse_error.109 if array index is not a number + @throw type_error.313 if value cannot be unflattened + */ + BasicJsonType& get_and_create(BasicJsonType& j) const + { + using size_type = typename BasicJsonType::size_type; + auto result = &j; + + // in case no reference tokens exist, return a reference to the JSON value + // j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case detail::value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + JSON_TRY + { + result = &result->operator[](static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + single value; that is, with an empty list of reference tokens. + */ + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries to + create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_unchecked(BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->m_type == detail::value_t::null) + { + // check if reference token is a number + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const char x) + { + return (x >= '0' and x <= '9'); + }); + + // change value to array for numbers or "-" or to object otherwise + *ptr = (nums or reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; + } + + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + if (reference_token == "-") + { + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_checked(BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" cannot be used for const access + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // use unchecked array access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_checked(const BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @brief split the string input to reference tokens + + @note This function is only called by the json_pointer constructor. + All exceptions below are documented there. + + @throw parse_error.107 if the pointer is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' + */ + static std::vector split(const std::string& reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (JSON_UNLIKELY(reference_string[0] != '/')) + { + JSON_THROW(detail::parse_error::create(107, 1, + "JSON pointer must be empty or begin with '/' - was: '" + + reference_string + "'")); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + std::size_t slash = reference_string.find_first_of('/', 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == string::npos+1 = 0 + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = slash + 1, + // find next slash + slash = reference_string.find_first_of('/', start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (std::size_t pos = reference_token.find_first_of('~'); + pos != std::string::npos; + pos = reference_token.find_first_of('~', pos + 1)) + { + assert(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (JSON_UNLIKELY(pos == reference_token.size() - 1 or + (reference_token[pos + 1] != '0' and + reference_token[pos + 1] != '1'))) + { + JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); + } + } + + // finally, store the reference token + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + /*! + @brief replace all occurrences of a substring by another string + + @param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t + @param[in] f the substring to replace with @a t + @param[in] t the string to replace @a f + + @pre The search string @a f must not be empty. **This precondition is + enforced with an assertion.** + + @since version 2.0.0 + */ + static void replace_substring(std::string& s, const std::string& f, + const std::string& t) + { + assert(not f.empty()); + 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 + {} + } + + /// escape "~"" to "~0" and "/" to "~1" + static std::string escape(std::string s) + { + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + /// unescape "~1" to tilde and "~0" to slash (order is important!) + static void unescape(std::string& s) + { + replace_substring(s, "~1", "/"); + replace_substring(s, "~0", "~"); + } + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const std::string& reference_string, + const BasicJsonType& value, + BasicJsonType& result) + { + switch (value.m_type) + { + case detail::value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (std::size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case detail::value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + + @throw parse_error.109 if array index is not a number + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + @throw type_error.313 if value cannot be unflattened + */ + static BasicJsonType + unflatten(const BasicJsonType& value) + { + if (JSON_UNLIKELY(not value.is_object())) + { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); + } + + BasicJsonType result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (JSON_UNLIKELY(not element.second.is_primitive())) + { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); + } + + // assign value to reference pointed to by JSON pointer; Note that if + // the JSON pointer is "" (i.e., points to the whole value), function + // get_and_create returns a reference to result itself. An assignment + // will then create a primitive value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + friend bool operator==(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return (lhs.reference_tokens == rhs.reference_tokens); + } + + friend bool operator!=(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return not (lhs == rhs); + } + + /// the reference tokens + std::vector reference_tokens; +}; +} + +// #include + + +#include + +// #include + +// #include + + +namespace nlohmann +{ +template +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @param[in] j JSON value to read from + @param[in,out] val value to write to + */ + template + static void from_json(BasicJsonType&& j, ValueType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward(j), val))) + { + ::nlohmann::from_json(std::forward(j), val); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref basic_json + class. + + @param[in,out] j JSON value to write to + @param[in] val value to read from + */ + template + static void to_json(BasicJsonType& j, ValueType&& val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward(val)))) + { + ::nlohmann::to_json(j, std::forward(val)); + } +}; +} + + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ + +/*! +@brief a class to store JSON values + +@tparam ObjectType type for JSON objects (`std::map` by default; will be used +in @ref object_t) +@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used +in @ref array_t) +@tparam StringType type for JSON strings and object keys (`std::string` by +default; will be used in @ref string_t) +@tparam BooleanType type for JSON booleans (`bool` by default; will be used +in @ref boolean_t) +@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by +default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) +@tparam NumberFloatType type for JSON floating-point numbers (`double` by +default; will be used in @ref number_float_t) +@tparam AllocatorType type of the allocator to use (`std::allocator` by +default) +@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` +and `from_json()` (@ref adl_serializer by default) + +@requirement The class satisfies the following concept requirements: +- Basic + - [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible): + JSON values can be default constructed. The result will be a JSON null + value. + - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): + A JSON value can be constructed from an rvalue argument. + - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): + A JSON value can be copy-constructed from an lvalue expression. + - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): + A JSON value van be assigned from an rvalue argument. + - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): + A JSON value can be copy-assigned from an lvalue expression. + - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible): + JSON values can be destructed. +- Layout + - [StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType): + JSON values have + [standard layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout): + All non-static data members are private and standard layout types, the + class has no virtual functions or (virtual) base classes. +- Library-wide + - [EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable): + JSON values can be compared with `==`, see @ref + operator==(const_reference,const_reference). + - [LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable): + JSON values can be compared with `<`, see @ref + operator<(const_reference,const_reference). + - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable): + Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of + other compatible types, using unqualified function call @ref swap(). + - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer): + JSON values can be compared against `std::nullptr_t` objects which are used + to model the `null` value. +- Container + - [Container](http://en.cppreference.com/w/cpp/concept/Container): + JSON values can be used like STL containers and provide iterator access. + - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer); + JSON values can be used like STL containers and provide reverse iterator + access. + +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@internal +@note ObjectType trick from http://stackoverflow.com/a/9860911 +@endinternal + +@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange +Format](http://rfc7159.net/rfc7159) + +@since version 1.0.0 + +@nosubgrouping +*/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +class basic_json +{ + private: + template friend struct detail::external_constructor; + friend ::nlohmann::json_pointer; + friend ::nlohmann::detail::parser; + friend ::nlohmann::detail::serializer; + template + friend class ::nlohmann::detail::iter_impl; + template + friend class ::nlohmann::detail::binary_writer; + template + friend class ::nlohmann::detail::binary_reader; + + /// workaround type for MSVC + using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + + // convenience aliases for types residing in namespace detail; + using lexer = ::nlohmann::detail::lexer; + using parser = ::nlohmann::detail::parser; + + using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; + template + using internal_iterator = ::nlohmann::detail::internal_iterator; + template + using iter_impl = ::nlohmann::detail::iter_impl; + template + using iteration_proxy = ::nlohmann::detail::iteration_proxy; + template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; + + template + using output_adapter_t = ::nlohmann::detail::output_adapter_t; + + using binary_reader = ::nlohmann::detail::binary_reader; + template using binary_writer = ::nlohmann::detail::binary_writer; + + using serializer = ::nlohmann::detail::serializer; + + public: + using value_t = detail::value_t; + /// @copydoc nlohmann::json_pointer + using json_pointer = ::nlohmann::json_pointer; + template + using json_serializer = JSONSerializer; + /// helper type for initializer lists of basic_json values + using initializer_list_t = std::initializer_list>; + + //////////////// + // exceptions // + //////////////// + + /// @name exceptions + /// Classes to implement user-defined exceptions. + /// @{ + + /// @copydoc detail::exception + using exception = detail::exception; + /// @copydoc detail::parse_error + using parse_error = detail::parse_error; + /// @copydoc detail::invalid_iterator + using invalid_iterator = detail::invalid_iterator; + /// @copydoc detail::type_error + using type_error = detail::type_error; + /// @copydoc detail::out_of_range + using out_of_range = detail::out_of_range; + /// @copydoc detail::other_error + using other_error = detail::other_error; + + /// @} + + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType; + + /// the type of an element pointer + using pointer = typename std::allocator_traits::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits::const_pointer; + + /// an iterator for a basic_json container + using iterator = iter_impl; + /// a const iterator for a basic_json container + using const_iterator = iter_impl; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator; + + /// @} + + + /*! + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() + { + return allocator_type(); + } + + /*! + @brief returns version information on the library + + This function returns a JSON object with information about the library, + including the version number and information on the platform and compiler. + + @return JSON object holding version information + key | description + ----------- | --------------- + `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). + `copyright` | The copyright line for the library as string. + `name` | The name of the library as string. + `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. + `url` | The URL of the project as string. + `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string). + + @liveexample{The following code shows an example output of the `meta()` + function.,meta} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @complexity Constant. + + @since 2.1.0 + */ + static basic_json meta() + { + basic_json result; + + result["copyright"] = "(C) 2013-2017 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["url"] = "https://github.com/nlohmann/json"; + result["version"]["string"] = + std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_PATCH); + result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; + result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; + result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; + +#ifdef _WIN32 + result["platform"] = "win32"; +#elif defined __linux__ + result["platform"] = "linux"; +#elif defined __APPLE__ + result["platform"] = "apple"; +#elif defined __unix__ + result["platform"] = "unix"; +#else + result["platform"] = "unknown"; +#endif + +#if defined(__ICC) || defined(__INTEL_COMPILER) + result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; +#elif defined(__clang__) + result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; +#elif defined(__GNUC__) || defined(__GNUG__) + result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; +#elif defined(__HP_cc) || defined(__HP_aCC) + result["compiler"] = "hp" +#elif defined(__IBMCPP__) + result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; +#elif defined(_MSC_VER) + result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; +#elif defined(__PGI) + result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; +#elif defined(__SUNPRO_CC) + result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; +#else + result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; +#endif + +#ifdef __cplusplus + result["compiler"]["c++"] = std::to_string(__cplusplus); +#else + result["compiler"]["c++"] = "unknown"; +#endif + return result; + } + + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + +#if defined(JSON_HAS_CPP_14) + // Use transparent comparator if possible, combined with perfect forwarding + // on find() and count() calls prevents unnecessary string construction. + using object_comparator_t = std::less<>; +#else + using object_comparator_t = std::less; +#endif + + /*! + @brief a type for an object + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: + > An object is an unordered collection of zero or more name/value pairs, + > where a name is a string and a value is a string, number, boolean, null, + > object, or array. + + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) + + #### Default type + + With the default values for @a ObjectType (`std::map`), @a StringType + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: + + @code {.cpp} + std::map< + std::string, // key_type + basic_json, // value_type + std::less, // key_compare + std::allocator> // allocator_type + > + @endcode + + #### Behavior + + The choice of @a object_t influences the behavior of the JSON class. With + the default type, objects have the following behavior: + + - When all names are unique, objects will be interoperable in the sense + that all software implementations receiving that object will agree on + the name-value mappings. + - When the names within an object are not unique, it is unspecified which + one of the values for a given key will be chosen. For instance, + `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or + `{"key": 2}`. + - Internally, name/value pairs are stored in lexicographical order of the + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. + - When comparing objects, the order of the name/value pairs is irrelevant. + This makes objects interoperable in the sense that they will not be + affected by these differences. For instance, `{"b": 1, "a": 2}` and + `{"a": 2, "b": 1}` will be treated as equal. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the object's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. + + #### Storage + + Objects are stored as pointers in a @ref basic_json type. That is, for any + access to object values, a pointer of type `object_t*` must be + dereferenced. + + @sa @ref array_t -- type for an array value + + @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. + */ + using object_t = ObjectType>>; + + /*! + @brief a type for an array + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: + > An array is an ordered sequence of zero or more values. + + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) + + #### Default type + + With the default values for @a ArrayType (`std::vector`) and @a + AllocatorType (`std::allocator`), the default value for @a array_t is: + + @code {.cpp} + std::vector< + basic_json, // value_type + std::allocator // allocator_type + > + @endcode + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the array's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. + + #### Storage + + Arrays are stored as pointers in a @ref basic_json type. That is, for any + access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa @ref object_t -- type for an object value + + @since version 1.0.0 + */ + using array_t = ArrayType>; + + /*! + @brief a type for a string + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: + > A string is a sequence of zero or more Unicode characters. + + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. + + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. + + #### Default type + + With the default values for @a StringType (`std::string`), the default + value for @a string_t is: + + @code {.cpp} + std::string + @endcode + + #### Encoding + + Strings are stored in UTF-8 encoding. Therefore, functions like + `std::string::size()` or `std::string::length()` return the number of + bytes in the string rather than the number of characters or glyphs. + + #### String comparison + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > Software implementations are typically required to test names of object + > members for equality. Implementations that transform the textual + > representation into sequences of Unicode code units and then perform the + > comparison numerically, code unit by code unit, are interoperable in the + > sense that implementations will agree in all cases on equality or + > inequality of two strings. For example, implementations that compare + > strings with escaped characters unconverted may incorrectly find that + > `"a\\b"` and `"a\u005Cb"` are not equal. + + This implementation is interoperable as it does compare strings code unit + by code unit. + + #### Storage + + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be + dereferenced. + + @since version 1.0.0 + */ + using string_t = StringType; + + /*! + @brief a type for a boolean + + [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a + type which differentiates the two literals `true` and `false`. + + To store objects in C++, a type is defined by the template parameter @a + BooleanType which chooses the type to use. + + #### Default type + + With the default values for @a BooleanType (`bool`), the default value for + @a boolean_t is: + + @code {.cpp} + bool + @endcode + + #### Storage + + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 + */ + using boolean_t = BooleanType; + + /*! + @brief a type for a number (integer) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store integer numbers in C++, a type is defined by the template + parameter @a NumberIntegerType which chooses the type to use. + + #### Default type + + With the default values for @a NumberIntegerType (`int64_t`), the default + value for @a number_integer_t is: + + @code {.cpp} + int64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `9223372036854775807` (INT64_MAX) and the minimal integer number + that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange of the exactly supported range [INT64_MIN, + INT64_MAX], this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_integer_t = NumberIntegerType; + + /*! + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], + this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + + /*! + @brief a type for a number (floating-point) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store floating-point numbers in C++, a type is defined by the template + parameter @a NumberFloatType which chooses the type to use. + + #### Default type + + With the default values for @a NumberFloatType (`double`), the default + value for @a number_float_t is: + + @code {.cpp} + double + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ + floating-point literal `01.2` will be serialized to `1.2`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > This specification allows implementations to set limits on the range and + > precision of numbers accepted. Since software that implements IEEE + > 754-2008 binary64 (double precision) numbers is generally available and + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected + > precision. + + This implementation does exactly follow this approach, as it uses double + precision floating-point numbers. Note values smaller than + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` + will be stored as NaN internally and be serialized to `null`. + + #### Storage + + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa @ref number_integer_t -- type for number values (integer) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_float_t = NumberFloatType; + + /// @} + + private: + + /// helper for exception-safe object creation + template + static T* create(Args&& ... args) + { + AllocatorType alloc; + using AllocatorTraits = std::allocator_traits>; + + auto deleter = [&](T * object) + { + AllocatorTraits::deallocate(alloc, object, 1); + }; + std::unique_ptr object(AllocatorTraits::allocate(alloc, 1), deleter); + AllocatorTraits::construct(alloc, object.get(), std::forward(args)...); + assert(object != nullptr); + return object.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create(); + break; + } + + case value_t::array: + { + array = create(); + break; + } + + case value_t::string: + { + string = create(""); + break; + } + + case value_t::boolean: + { + boolean = boolean_t(false); + break; + } + + case value_t::number_integer: + { + number_integer = number_integer_t(0); + break; + } + + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } + + case value_t::number_float: + { + number_float = number_float_t(0.0); + break; + } + + case value_t::null: + { + object = nullptr; // silence warning, see #821 + break; + } + + default: + { + object = nullptr; // silence warning, see #821 + if (JSON_UNLIKELY(t == value_t::null)) + { + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.1.1")); // LCOV_EXCL_LINE + } + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) + { + string = create(value); + } + + /// constructor for rvalue strings + json_value(string_t&& value) + { + string = create(std::move(value)); + } + + /// constructor for objects + json_value(const object_t& value) + { + object = create(value); + } + + /// constructor for rvalue objects + json_value(object_t&& value) + { + object = create(std::move(value)); + } + + /// constructor for arrays + json_value(const array_t& value) + { + array = create(value); + } + + /// constructor for rvalue arrays + json_value(array_t&& value) + { + array = create(std::move(value)); + } + + void destroy(value_t t) noexcept + { + switch (t) + { + case value_t::object: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, object); + std::allocator_traits::deallocate(alloc, object, 1); + break; + } + + case value_t::array: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, array); + std::allocator_traits::deallocate(alloc, array, 1); + break; + } + + case value_t::string: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, string); + std::allocator_traits::deallocate(alloc, string, 1); + break; + } + + default: + { + break; + } + } + } + }; + + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + */ + void assert_invariant() const noexcept + { + assert(m_type != value_t::object or m_value.object != nullptr); + assert(m_type != value_t::array or m_value.array != nullptr); + assert(m_type != value_t::string or m_value.string != nullptr); + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /*! + @brief parser event types + + The parser callback distinguishes the following events: + - `object_start`: the parser read `{` and started to process a JSON object + - `key`: the parser read a key of a value in an object + - `object_end`: the parser read `}` and finished processing a JSON object + - `array_start`: the parser read `[` and started to process a JSON array + - `array_end`: the parser read `]` and finished processing a JSON array + - `value`: the parser finished reading a JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + @sa @ref parser_callback_t for more information and examples + */ + using parse_event_t = typename parser::parse_event_t; + + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse, it is called on certain events + (passed as @ref parse_event_t via parameter @a event) with a set recursion + depth @a depth and context JSON value @a parsed. The return value of the + callback function is a boolean indicating whether the element that emitted + the callback shall be kept or not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa @ref parse for examples + + @since version 1.0.0 + */ + using parser_callback_t = typename parser::parser_callback_t; + + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /*! + @brief create an empty value with a given type + + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @param[in] v the type of the value to create + + @complexity Constant. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @sa @ref clear() -- restores the postcondition of this constructor + + @since version 1.0.0 + */ + basic_json(const value_t v) + : m_type(v), m_value(v) + { + assert_invariant(); + } + + /*! + @brief create a null object + + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} + + @since version 1.0.0 + */ + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) + { + assert_invariant(); + } + + /*! + @brief create a JSON value + + This is a "catch all" constructor for all compatible JSON types; that is, + types for which a `to_json()` method exists. The constructor forwards the + parameter @a val to that method (to `json_serializer::to_json` method + with `U = uncvref_t`, to be exact). + + Template type @a CompatibleType includes, but is not limited to, the + following types: + - **arrays**: @ref array_t and all kinds of compatible containers such as + `std::vector`, `std::deque`, `std::list`, `std::forward_list`, + `std::array`, `std::valarray`, `std::set`, `std::unordered_set`, + `std::multiset`, and `std::unordered_multiset` with a `value_type` from + which a @ref basic_json value can be constructed. + - **objects**: @ref object_t and all kinds of compatible associative + containers such as `std::map`, `std::unordered_map`, `std::multimap`, + and `std::unordered_multimap` with a `key_type` compatible to + @ref string_t and a `value_type` from which a @ref basic_json value can + be constructed. + - **strings**: @ref string_t, string literals, and all compatible string + containers can be used. + - **numbers**: @ref number_integer_t, @ref number_unsigned_t, + @ref number_float_t, and all convertible number types such as `int`, + `size_t`, `int64_t`, `float` or `double` can be used. + - **boolean**: @ref boolean_t / `bool` can be used. + + See the examples below. + + @tparam CompatibleType a type such that: + - @a CompatibleType is not derived from `std::istream`, + - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move + constructors), + - @a CompatibleType is not a @ref basic_json nested type (e.g., + @ref json_pointer, @ref iterator, etc ...) + - @ref @ref json_serializer has a + `to_json(basic_json_t&, CompatibleType&&)` method + + @tparam U = `uncvref_t` + + @param[in] val the value to be forwarded to the respective constructor + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @liveexample{The following code shows the constructor with several + compatible types.,basic_json__CompatibleType} + + @since version 2.1.0 + */ + template , + detail::enable_if_t< + detail::is_compatible_type::value, int> = 0> + basic_json(CompatibleType && val) noexcept(noexcept( + JSONSerializer::to_json(std::declval(), + std::forward(val)))) + { + JSONSerializer::to_json(*this, std::forward(val)); + assert_invariant(); + } + + /*! + @brief create a container (array or object) from an initializer list + + Creates a JSON value of type array or object from the passed initializer + list @a init. In case @a type_deduction is `true` (default), the type of + the JSON value to be created is deducted from the initializer list @a init + according to the following rules: + + 1. If the list is empty, an empty JSON object value `{}` is created. + 2. If the list consists of pairs whose first element is a string, a JSON + object value is created where the first elements of the pairs are + treated as keys and the second elements are as values. + 3. In all other cases, an array is created. + + The rules aim to create the best fit between a C++ initializer list and + JSON values. The rationale is as follows: + + 1. The empty initializer list is written as `{}` which is exactly an empty + JSON object. + 2. C++ has no way of describing mapped types other than to list a list of + pairs. As JSON requires that keys must be of type string, rule 2 is the + weakest constraint one can pose on initializer lists to interpret them + as an object. + 3. In all other cases, the initializer list could not be interpreted as + JSON object type, so interpreting it as JSON array type is safe. + + With the rules described above, the following JSON values cannot be + expressed by an initializer list: + + - the empty array (`[]`): use @ref array(initializer_list_t) + with an empty initializer list in this case + - arrays whose elements satisfy rule 2: use @ref + array(initializer_list_t) with the same initializer list + in this case + + @note When used without parentheses around an empty initializer list, @ref + basic_json() is called instead of this function, yielding the JSON null + value. + + @param[in] init initializer list with JSON values + + @param[in] type_deduction internal parameter; when set to `true`, the type + of the JSON value is deducted from the initializer list @a init; when set + to `false`, the type provided via @a manual_type is forced. This mode is + used by the functions @ref array(initializer_list_t) and + @ref object(initializer_list_t). + + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref + value_t::array and @ref value_t::object are valid); when @a type_deduction + is set to `true`, this parameter has no effect + + @throw type_error.301 if @a type_deduction is `false`, @a manual_type is + `value_t::object`, but @a init contains an element which is not a pair + whose first element is a string. In this case, the constructor could not + create an object. If @a type_deduction would have be `true`, an array + would have been created. See @ref object(initializer_list_t) + for an example. + + @complexity Linear in the size of the initializer list @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows how JSON values are created from + initializer lists.,basic_json__list_init_t} + + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + basic_json(initializer_list_t init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const detail::json_ref& element_ref) + { + return (element_ref->is_array() and element_ref->size() == 2 and (*element_ref)[0].is_string()); + }); + + // adjust type if type deduction is not wanted + if (not type_deduction) + { + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if object is wanted but impossible, throw an exception + if (JSON_UNLIKELY(manual_type == value_t::object and not is_an_object)) + { + JSON_THROW(type_error::create(301, "cannot create object from initializer list")); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create object + m_type = value_t::object; + m_value = value_t::object; + + std::for_each(init.begin(), init.end(), [this](const detail::json_ref& element_ref) + { + auto element = element_ref.moved_or_copied(); + m_value.object->emplace( + std::move(*((*element.m_value.array)[0].m_value.string)), + std::move((*element.m_value.array)[1])); + }); + } + else + { + // the initializer list describes an array -> create array + m_type = value_t::array; + m_value.array = create(init.begin(), init.end()); + } + + assert_invariant(); + } + + /*! + @brief explicitly create an array from an initializer list + + Creates a JSON array value from a given initializer list. That is, given a + list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the + initializer list is empty, the empty array `[]` is created. + + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref + basic_json(initializer_list_t, bool, value_t)). These cases + are: + 1. creating an array whose elements are all pairs whose first element is a + string -- in this case, the initializer list constructor would create an + object, taking the first elements as keys + 2. creating an empty array -- passing the empty initializer list to the + initializer list constructor yields an empty object + + @param[in] init initializer list with JSON values to create an array from + (optional) + + @return JSON array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `array` + function.,array} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + static basic_json array(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::array); + } + + /*! + @brief explicitly create an object from an initializer list + + Creates a JSON object value from a given initializer list. The initializer + lists elements must be pairs, and their first elements must be strings. If + the initializer list is empty, the empty object `{}` is created. + + @note This function is only added for symmetry reasons. In contrast to the + related function @ref array(initializer_list_t), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor @ref basic_json(initializer_list_t, bool, value_t). + + @param[in] init initializer list to create an object from (optional) + + @return JSON object value + + @throw type_error.301 if @a init is not a list of pairs whose first + elements are strings. In this case, no object can be created. When such a + value is passed to @ref basic_json(initializer_list_t, bool, value_t), + an array would have been created from the passed initializer list @a init. + See example below. + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `object` + function.,object} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + + @since version 1.0.0 + */ + static basic_json object(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::object); + } + + /*! + @brief construct an array with count copies of given value + + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. + + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy + + @post `std::distance(begin(),end()) == cnt` holds. + + @complexity Linear in @a cnt. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows examples for the @ref + basic_json(size_type\, const basic_json&) + constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 + */ + basic_json(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create(cnt, val); + assert_invariant(); + } + + /*! + @brief construct a JSON container given an iterator range + + Constructs the JSON value with the contents of the range `[first, last)`. + The semantics depends on the different types a JSON value can have: + - In case of a null type, invalid_iterator.206 is thrown. + - In case of other primitive types (number, boolean, or string), @a first + must be `begin()` and @a last must be `end()`. In this case, the value is + copied. Otherwise, invalid_iterator.204 is thrown. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector` or `std::map`; that is, a JSON array + or object is constructed from the values in the range. + + @tparam InputIT an input iterator type (@ref iterator or @ref + const_iterator) + + @param[in] first begin of the range to copy from (included) + @param[in] last end of the range to copy from (excluded) + + @pre Iterators @a first and @a last must be initialized. **This + precondition is enforced with an assertion (see warning).** If + assertions are switched off, a violation of this precondition yields + undefined behavior. + + @pre Range `[first, last)` is valid. Usually, this precondition cannot be + checked efficiently. Only certain edge cases are detected; see the + description of the exceptions below. A violation of this precondition + yields undefined behavior. + + @warning A precondition is enforced with a runtime assertion that will + result in calling `std::abort` if this precondition is not met. + Assertions can be disabled by defining `NDEBUG` at compile time. + See http://en.cppreference.com/w/cpp/error/assert for more + information. + + @throw invalid_iterator.201 if iterators @a first and @a last are not + compatible (i.e., do not belong to the same JSON value). In this case, + the range `[first, last)` is undefined. + @throw invalid_iterator.204 if iterators @a first and @a last belong to a + primitive type (number, boolean, or string), but @a first does not point + to the first element any more. In this case, the range `[first, last)` is + undefined. See example code below. + @throw invalid_iterator.206 if iterators @a first and @a last belong to a + null value. In this case, the range `[first, last)` is undefined. + + @complexity Linear in distance between @a first and @a last. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows several ways to create JSON values by + specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type = 0> + basic_json(InputIT first, InputIT last) + { + assert(first.m_object != nullptr); + assert(last.m_object != nullptr); + + // make sure iterator fits the current value + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); + } + + // copy type from first iterator + m_type = first.m_object->m_type; + + // check if iterator range is complete for primitive values + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_UNLIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + break; + } + + default: + break; + } + + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value.number_float = first.m_object->m_value.number_float; + break; + } + + case value_t::boolean: + { + m_value.boolean = first.m_object->m_value.boolean; + break; + } + + case value_t::string: + { + m_value = *first.m_object->m_value.string; + break; + } + + case value_t::object: + { + m_value.object = create(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_value.array = create(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + + std::string(first.m_object->type_name()))); + } + + assert_invariant(); + } + + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + /// @private + basic_json(const detail::json_ref& ref) + : basic_json(ref.moved_or_copied()) + {} + + /*! + @brief copy constructor + + Creates a copy of a given JSON value. + + @param[in] other the JSON value to copy + + @post `*this == other` + + @complexity Linear in the size of @a other. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - As postcondition, it holds: `other == basic_json(other)`. + + @liveexample{The following code shows an example for the copy + constructor.,basic_json__basic_json} + + @since version 1.0.0 + */ + basic_json(const basic_json& other) + : m_type(other.m_type) + { + // check of passed value is valid + other.assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + m_value = *other.m_value.object; + break; + } + + case value_t::array: + { + m_value = *other.m_value.array; + break; + } + + case value_t::string: + { + m_value = *other.m_value.string; + break; + } + + case value_t::boolean: + { + m_value = other.m_value.boolean; + break; + } + + case value_t::number_integer: + { + m_value = other.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value = other.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value = other.m_value.number_float; + break; + } + + default: + break; + } + + assert_invariant(); + } + + /*! + @brief move constructor + + Move constructor. Constructs a JSON value with the contents of the given + value @a other using move semantics. It "steals" the resources from @a + other and leaves it as JSON null value. + + @param[in,out] other value to move to this object + + @post `*this` has the same value as @a other before the call. + @post @a other is a JSON null value. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @requirement This function helps `basic_json` satisfying the + [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible) + requirements. + + @liveexample{The code below shows the move constructor explicitly called + via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 + */ + basic_json(basic_json&& other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) + { + // check that passed value is valid + other.assert_invariant(); + + // invalidate payload + other.m_type = value_t::null; + other.m_value = {}; + + assert_invariant(); + } + + /*! + @brief copy assignment + + Copy assignment operator. Copies a JSON value via the "copy and swap" + strategy: It is expressed in terms of the copy constructor, destructor, + and the `swap()` member function. + + @param[in] other value to copy from + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + + @liveexample{The code below shows and example for the copy assignment. It + creates a copy of value `a` which is then swapped with `b`. Finally\, the + copy of `a` (which is the null value after the swap) is + destroyed.,basic_json__copyassignment} + + @since version 1.0.0 + */ + reference& operator=(basic_json other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + // check that passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + + assert_invariant(); + return *this; + } + + /*! + @brief destructor + + Destroys the JSON value and frees all allocated memory. + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 + */ + ~basic_json() noexcept + { + assert_invariant(); + m_value.destroy(m_type); + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /*! + @brief serialization + + Serialization function for JSON values. The function tries to mimic + Python's `json.dumps()` function, and currently supports its @a indent + and @a ensure_ascii parameters. + + @param[in] indent If indent is nonnegative, then array elements and object + members will be pretty-printed with that indent level. An indent level of + `0` will only insert newlines. `-1` (the default) selects the most compact + representation. + @param[in] indent_char The character to use for indentation if @a indent is + greater than `0`. The default is ` ` (space). + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists + of ASCII characters only. + + @return string containing the serialization of the JSON value + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + + @complexity Linear. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @liveexample{The following example shows the effect of different @a indent\, + @a indent_char\, and @a ensure_ascii parameters to the result of the + serialization.,dump} + + @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0; indentation character @a indent_char, option + @a ensure_ascii and exceptions added in version 3.0.0 + */ + string_t dump(const int indent = -1, const char indent_char = ' ', + const bool ensure_ascii = false) const + { + string_t result; + serializer s(detail::output_adapter(result), indent_char); + + if (indent >= 0) + { + s.dump(*this, true, ensure_ascii, static_cast(indent)); + } + else + { + s.dump(*this, false, ensure_ascii, 0); + } + + return result; + } + + /*! + @brief return the type of the JSON value (explicit) + + Return the type of the JSON value as a value from the @ref value_t + enumeration. + + @return the type of the JSON value + Value type | return value + ------------------------- | ------------------------- + null | value_t::null + boolean | value_t::boolean + string | value_t::string + number (integer) | value_t::number_integer + number (unsigned integer) | value_t::number_unsigned + number (floating-point) | value_t::number_float + object | value_t::object + array | value_t::array + discarded | value_t::discarded + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `type()` for all JSON + types.,type} + + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr value_t type() const noexcept + { + return m_type; + } + + /*! + @brief return whether type is primitive + + This function returns true if and only if the JSON type is primitive + (string, number, boolean, or null). + + @return `true` if type is primitive (string, number, boolean, or null), + `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_primitive()` for all JSON + types.,is_primitive} + + @sa @ref is_structured() -- returns whether JSON value is structured + @sa @ref is_null() -- returns whether JSON value is `null` + @sa @ref is_string() -- returns whether JSON value is a string + @sa @ref is_boolean() -- returns whether JSON value is a boolean + @sa @ref is_number() -- returns whether JSON value is a number + + @since version 1.0.0 + */ + constexpr bool is_primitive() const noexcept + { + return is_null() or is_string() or is_boolean() or is_number(); + } + + /*! + @brief return whether type is structured + + This function returns true if and only if the JSON type is structured + (array or object). + + @return `true` if type is structured (array or object), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_structured()` for all JSON + types.,is_structured} + + @sa @ref is_primitive() -- returns whether value is primitive + @sa @ref is_array() -- returns whether value is an array + @sa @ref is_object() -- returns whether value is an object + + @since version 1.0.0 + */ + constexpr bool is_structured() const noexcept + { + return is_array() or is_object(); + } + + /*! + @brief return whether value is null + + This function returns true if and only if the JSON value is null. + + @return `true` if type is null, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_null()` for all JSON + types.,is_null} + + @since version 1.0.0 + */ + constexpr bool is_null() const noexcept + { + return (m_type == value_t::null); + } + + /*! + @brief return whether value is a boolean + + This function returns true if and only if the JSON value is a boolean. + + @return `true` if type is boolean, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_boolean()` for all JSON + types.,is_boolean} + + @since version 1.0.0 + */ + constexpr bool is_boolean() const noexcept + { + return (m_type == value_t::boolean); + } + + /*! + @brief return whether value is a number + + This function returns true if and only if the JSON value is a number. This + includes both integer (signed and unsigned) and floating-point values. + + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number()` for all JSON + types.,is_number} + + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number() const noexcept + { + return is_number_integer() or is_number_float(); + } + + /*! + @brief return whether value is an integer number + + This function returns true if and only if the JSON value is a signed or + unsigned integer number. This excludes floating-point values. + + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_integer()` for all + JSON types.,is_number_integer} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number_integer() const noexcept + { + return (m_type == value_t::number_integer or m_type == value_t::number_unsigned); + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true if and only if the JSON value is an unsigned + integer number. This excludes floating-point and signed integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_unsigned()` for all + JSON types.,is_number_unsigned} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + constexpr bool is_number_unsigned() const noexcept + { + return (m_type == value_t::number_unsigned); + } + + /*! + @brief return whether value is a floating-point number + + This function returns true if and only if the JSON value is a + floating-point number. This excludes signed and unsigned integer values. + + @return `true` if type is a floating-point number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_float()` for all + JSON types.,is_number_float} + + @sa @ref is_number() -- check if value is number + @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + + @since version 1.0.0 + */ + constexpr bool is_number_float() const noexcept + { + return (m_type == value_t::number_float); + } + + /*! + @brief return whether value is an object + + This function returns true if and only if the JSON value is an object. + + @return `true` if type is object, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_object()` for all JSON + types.,is_object} + + @since version 1.0.0 + */ + constexpr bool is_object() const noexcept + { + return (m_type == value_t::object); + } + + /*! + @brief return whether value is an array + + This function returns true if and only if the JSON value is an array. + + @return `true` if type is array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_array()` for all JSON + types.,is_array} + + @since version 1.0.0 + */ + constexpr bool is_array() const noexcept + { + return (m_type == value_t::array); + } + + /*! + @brief return whether value is a string + + This function returns true if and only if the JSON value is a string. + + @return `true` if type is string, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_string()` for all JSON + types.,is_string} + + @since version 1.0.0 + */ + constexpr bool is_string() const noexcept + { + return (m_type == value_t::string); + } + + /*! + @brief return whether value is discarded + + This function returns true if and only if the JSON value was discarded + during parsing with a callback function (see @ref parser_callback_t). + + @note This function will always be `false` for JSON values after parsing. + That is, discarded values can only occur during parsing, but will be + removed when inside a structured value or replaced by null in other cases. + + @return `true` if type is discarded, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_discarded()` for all JSON + types.,is_discarded} + + @since version 1.0.0 + */ + constexpr bool is_discarded() const noexcept + { + return (m_type == value_t::discarded); + } + + /*! + @brief return the type of the JSON value (implicit) + + Implicitly return the type of the JSON value as a value from the @ref + value_t enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies the @ref value_t operator for + all JSON types.,operator__value_t} + + @sa @ref type() -- return the type of the JSON value (explicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept + { + return m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get a boolean (explicit) + boolean_t get_impl(boolean_t* /*unused*/) const + { + if (JSON_LIKELY(is_boolean())) + { + return m_value.boolean; + } + + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name()))); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t* /*unused*/) noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t* /*unused*/) noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t* /*unused*/) noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This function helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw type_error.303 if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // delegate the call to get_ptr<>() + auto ptr = obj.template get_ptr::type>(); + + if (JSON_LIKELY(ptr != nullptr)) + { + return *ptr; + } + + JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name()))); + } + + public: + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template::type, basic_json_t>::value, + int> = 0> + basic_json get() const + { + return *this; + } + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value + which is [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const basic_json&, ValueType&)`, and + - @ref json_serializer does not have a `from_json()` method of + the form `ValueType from_json(const basic_json&)` + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get__ValueType_const} + + @since version 2.1.0 + */ + template, + detail::enable_if_t < + not std::is_same::value and + detail::has_from_json::value and + not detail::has_non_default_from_json::value, + int> = 0> + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), std::declval()))) + { + // we cannot static_assert on ValueTypeCV being non-const, because + // there is support for get(), which is why we + // still need the uncvref + static_assert(not std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + static_assert(std::is_default_constructible::value, + "types must be DefaultConstructible when used with get()"); + + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + } + + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer has a `from_json()` method of the form + `ValueType from_json(const basic_json&)` + + @note If @ref json_serializer has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @since version 2.1.0 + */ + template, + detail::enable_if_t::value and + detail::has_non_default_from_json::value, + int> = 0> + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval()))) + { + static_assert(not std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return JSONSerializer::from_json(*this); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get() noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template::value, int>::type = 0> + constexpr const PointerType get() const noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (implicit) + + Implicit pointer access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the pointee of the result yields an undefined + state. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. Enforced by a static + assertion. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get_ptr} + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get_ptr() noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (implicit) + @copydoc get_ptr() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + constexpr const PointerType get_ptr() const noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a reference value (implicit) + + Implicit reference access to the internally stored JSON value. No copies + are made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. Enforced by static assertion. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + type_error.303 otherwise + + @throw type_error.303 in case passed type @a ReferenceType is incompatible + with the stored JSON value; see example below + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.1.0 + */ + template::value, int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw type_error.302 in case passed type @a ValueType is incompatible + to the JSON value type (e.g., the JSON value is of type boolean, but a + string is requested); see example below + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename std::enable_if < + not std::is_pointer::value and + not std::is_same>::value and + not std::is_same::value +#ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015 + and not std::is_same>::value +#endif +#if defined(JSON_HAS_CPP_17) + and not std::is_same::value +#endif + , int >::type = 0 > + operator ValueType() const + { + // delegate the call to get<>() const + return get(); + } + + /// @} + + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__size_type} + */ + reference at(size_type idx) + { + // at only works for arrays + if (JSON_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, + with bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__size_type_const} + */ + const_reference at(size_type idx) const + { + // at only works for arrays + if (JSON_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__object_t_key_type} + */ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, + with bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__object_t_key_type_const} + */ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array or null; in that + cases, using the [] operator with an index makes no sense. + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using `[]` operator. Note the addition of `null` + values.,operatorarray__size_type} + + @since version 1.0.0 + */ + reference operator[](size_type idx) + { + // implicitly convert null value to an empty array + if (is_null()) + { + m_type = value_t::array; + m_value.array = create(); + assert_invariant(); + } + + // operator[] only works for arrays + if (JSON_LIKELY(is_array())) + { + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) + { + m_value.array->insert(m_value.array->end(), + idx - m_value.array->size() + 1, + basic_json()); + } + + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array; in that case, + using the [] operator with an index makes no sense. + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the `[]` operator.,operatorarray__size_type_const} + + @since version 1.0.0 + */ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (JSON_LIKELY(is_array())) + { + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference operator[](const typename object_t::key_type& key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (JSON_LIKELY(is_object())) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + reference operator[](T* key) + { + // implicitly convert null to object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // at only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + const_reference operator[](T* key) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.306 if the JSON value is not an object; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + template::value, int>::type = 0> + ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + + return default_value; + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /*! + @brief access specified object element via JSON Pointer with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(ptr); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const json_pointer&), this function does not throw + if the given key @a key was not found. + + @param[in] ptr a JSON pointer to the element to access + @param[in] default_value the value to return if @a ptr found no value + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.306 if the JSON value is not an objec; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value_ptr} + + @sa @ref operator[](const json_pointer&) for unchecked access by reference + + @since version 2.0.2 + */ + template::value, int>::type = 0> + ValueType value(const json_pointer& ptr, const ValueType& default_value) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + // if pointer resolves a value, return it or use default value + JSON_TRY + { + return ptr.get_checked(this); + } + JSON_CATCH (out_of_range&) + { + return default_value; + } + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const json_pointer&, ValueType) const + */ + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /*! + @brief access the first element + + Returns a reference to the first element in the container. For a JSON + container `c`, the expression `c.front()` is equivalent to `*c.begin()`. + + @return In case of a structured type (array or object), a reference to the + first element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on `null` value + + @liveexample{The following code shows an example for `front()`.,front} + + @sa @ref back() -- access the last element + + @since version 1.0.0 + */ + reference front() + { + return *begin(); + } + + /*! + @copydoc basic_json::front() + */ + const_reference front() const + { + return *cbegin(); + } + + /*! + @brief access the last element + + Returns a reference to the last element in the container. For a JSON + container `c`, the expression `c.back()` is equivalent to + @code {.cpp} + auto tmp = c.end(); + --tmp; + return *tmp; + @endcode + + @return In case of a structured type (array or object), a reference to the + last element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on a `null` value. See example + below. + + @liveexample{The following code shows an example for `back()`.,back} + + @sa @ref front() -- access the first element + + @since version 1.0.0 + */ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /*! + @copydoc basic_json::back() + */ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /*! + @brief remove element given an iterator + + Removes the element specified by iterator @a pos. The iterator @a pos must + be valid and dereferenceable. Thus the `end()` iterator (which is valid, + but is not dereferenceable) cannot be used as a value for @a pos. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] pos iterator to the element to remove + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.202 if called on an iterator which does not belong + to the current JSON value; example: `"iterator does not fit current + value"` + @throw invalid_iterator.205 if called on a primitive type with invalid + iterator (i.e., any iterator which is not `begin()`); example: `"iterator + out of range"` + + @complexity The complexity depends on the type: + - objects: amortized constant + - arrays: linear in distance between @a pos and the end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType} + + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType pos) + { + // make sure iterator fits the current value + if (JSON_UNLIKELY(this != pos.m_object)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_UNLIKELY(not pos.m_it.primitive_iterator.is_begin())) + { + JSON_THROW(invalid_iterator::create(205, "iterator out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove elements given an iterator range + + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] first iterator to the beginning of the range to remove + @param[in] last iterator past the end of the range to remove + @return Iterator following the last removed element. If the iterator @a + second refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.203 if called on iterators which does not belong + to the current JSON value; example: `"iterators do not fit current value"` + @throw invalid_iterator.204 if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` + + @complexity The complexity depends on the type: + - objects: `log(size()) + std::distance(first, last)` + - arrays: linear in the distance between @a first and @a last, plus linear + in the distance between @a last and end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType_IteratorType} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType first, IteratorType last) + { + // make sure iterator fits the current value + if (JSON_UNLIKELY(this != first.m_object or this != last.m_object)) + { + JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_LIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove element from a JSON object given a key + + Removes elements from a JSON object with the key value @a key. + + @param[in] key value of the elements to remove + + @return Number of elements removed. If @a ObjectType is the default + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). + + @post References and iterators to the erased elements are invalidated. + Other references and iterators are not affected. + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + + @complexity `log(size()) + count(key)` + + @liveexample{The example shows the effect of `erase()`.,erase__key_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + size_type erase(const typename object_t::key_type& key) + { + // this erase only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->erase(key); + } + + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + /*! + @brief remove element from a JSON array given an index + + Removes element from a JSON array at the index @a idx. + + @param[in] idx index of the element to remove + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + @throw out_of_range.401 when `idx >= size()`; example: `"array index 17 + is out of range"` + + @complexity Linear in distance between @a idx and the end of the container. + + @liveexample{The example shows the effect of `erase()`.,erase__size_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 + */ + void erase(const size_type idx) + { + // this erase only works for arrays + if (JSON_LIKELY(is_array())) + { + if (JSON_UNLIKELY(idx >= size())) + { + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else + { + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + } + + /// @} + + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /*! + @brief find an element in a JSON object + + Finds an element in a JSON object with key equivalent to @a key. If the + element is not found or the JSON value is not an object, end() is + returned. + + @note This method always returns @ref end() when executed on a JSON type + that is not an object. + + @param[in] key key value of the element to search for. + + @return Iterator to an element with key equivalent to @a key. If no such + element is found or the JSON value is not an object, past-the-end (see + @ref end()) iterator is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `find()` is used.,find__key_type} + + @since version 1.0.0 + */ + template + iterator find(KeyT&& key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief find an element in a JSON object + @copydoc find(KeyT&&) + */ + template + const_iterator find(KeyT&& key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief returns the number of occurrences of a key in a JSON object + + Returns the number of elements with key @a key. If ObjectType is the + default `std::map` type, the return value will always be `0` (@a key was + not found) or `1` (@a key was found). + + @note This method always returns `0` when executed on a JSON type that is + not an object. + + @param[in] key key value of the element to count + + @return Number of elements with key @a key. If the JSON value is not an + object, the return value will be `0`. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `count()` is used.,count} + + @since version 1.0.0 + */ + template + size_type count(KeyT&& key) const + { + // return 0 for all nonobject types + return is_object() ? m_value.object->count(std::forward(key)) : 0; + } + + /// @} + + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /*! + @brief returns an iterator to the first element + + Returns an iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `begin()`.,begin} + + @sa @ref cbegin() -- returns a const iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /*! + @copydoc basic_json::cbegin() + */ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /*! + @brief returns a const iterator to the first element + + Returns a const iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).begin()`. + + @liveexample{The following code shows an example for `cbegin()`.,cbegin} + + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /*! + @brief returns an iterator to one past the last element + + Returns an iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `end()`.,end} + + @sa @ref cend() -- returns a const iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /*! + @copydoc basic_json::cend() + */ + const_iterator end() const noexcept + { + return cend(); + } + + /*! + @brief returns a const iterator to one past the last element + + Returns a const iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).end()`. + + @liveexample{The following code shows an example for `cend()`.,cend} + + @sa @ref end() -- returns an iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /*! + @brief returns an iterator to the reverse-beginning + + Returns an iterator to the reverse-beginning; that is, the last element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(end())`. + + @liveexample{The following code shows an example for `rbegin()`.,rbegin} + + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /*! + @copydoc basic_json::crbegin() + */ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /*! + @brief returns an iterator to the reverse-end + + Returns an iterator to the reverse-end; that is, one before the first + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(begin())`. + + @liveexample{The following code shows an example for `rend()`.,rend} + + @sa @ref crend() -- returns a const reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /*! + @copydoc basic_json::crend() + */ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /*! + @brief returns a const reverse iterator to the last element + + Returns a const iterator to the reverse-beginning; that is, the last + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rbegin()`. + + @liveexample{The following code shows an example for `crbegin()`.,crbegin} + + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /*! + @brief returns a const reverse iterator to one before the first + + Returns a const reverse iterator to the reverse-end; that is, one before + the first element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rend()`. + + @liveexample{The following code shows an example for `crend()`.,crend} + + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without iterator_wrapper: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without iterator proxy: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with iterator proxy: + + @code{cpp} + for (auto it : json::iterator_wrapper(j_object)) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). + + @param[in] ref reference to a JSON value + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the wrapper is used,iterator_wrapper} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @note The name of this function is not yet final and may change in the + future. + + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use @ref items() instead; + that is, replace `json::iterator_wrapper(j)` with `j.items()`. + */ + JSON_DEPRECATED + static iteration_proxy iterator_wrapper(reference ref) noexcept + { + return ref.items(); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + JSON_DEPRECATED + static iteration_proxy iterator_wrapper(const_reference ref) noexcept + { + return ref.items(); + } + + /*! + @brief helper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without `items()` function: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without `items()` function: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with `items()` function: + + @code{cpp} + for (auto it : j_object.items()) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). For primitive types (e.g., numbers), + `key()` returns an empty string. + + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the function is used.,items} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 3.x.x. + */ + iteration_proxy items() noexcept + { + return iteration_proxy(*this); + } + + /*! + @copydoc items() + */ + iteration_proxy items() const noexcept + { + return iteration_proxy(*this); + } + + /// @} + + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /*! + @brief checks whether the container is empty. + + Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `true` + boolean | `false` + string | `false` + number | `false` + object | result of function `object_t::empty()` + array | result of function `array_t::empty()` + + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `begin() == end()`. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + bool empty() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_value.object->empty(); + } + + default: + { + // all other types are nonempty + return false; + } + } + } + + /*! + @brief returns the number of elements + + Returns the number of elements in a JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` + boolean | `1` + string | `1` + number | `1` + object | result of function object_t::size() + array | result of function array_t::size() + + @liveexample{The following code calls `size()` on the different value + types.,size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `std::distance(begin(), end())`. + + @sa @ref empty() -- checks whether the container is empty + @sa @ref max_size() -- returns the maximal number of elements + + @since version 1.0.0 + */ + size_type size() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_value.object->size(); + } + + default: + { + // all other types have size 1 + return 1; + } + } + } + + /*! + @brief returns the maximum possible number of elements + + Returns the maximum number of elements a JSON value is able to hold due to + system or library implementation limitations, i.e. `std::distance(begin(), + end())` for the JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` (same as `size()`) + boolean | `1` (same as `size()`) + string | `1` (same as `size()`) + number | `1` (same as `size()`) + object | result of function `object_t::max_size()` + array | result of function `array_t::max_size()` + + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of returning `b.size()` where `b` is the largest + possible JSON value. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + size_type max_size() const noexcept + { + switch (m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_value.object->max_size(); + } + + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /*! + @brief clears the contents + + Clears the content of a JSON value and resets it to the default value as + if @ref basic_json(value_t) would have been called with the current value + type from @ref type(): + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @post Has the same effect as calling + @code {.cpp} + *this = basic_json(type()); + @endcode + + @liveexample{The example below shows the effect of `clear()` to different + JSON types.,clear} + + @complexity Linear in the size of the JSON value. + + @iterators All iterators, pointers and references related to this container + are invalidated. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @sa @ref basic_json(value_t) -- constructor that creates an object with the + same value than calling `clear()` + + @since version 1.0.0 + */ + void clear() noexcept + { + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: + { + m_value.number_float = 0.0; + break; + } + + case value_t::boolean: + { + m_value.boolean = false; + break; + } + + case value_t::string: + { + m_value.string->clear(); + break; + } + + case value_t::array: + { + m_value.array->clear(); + break; + } + + case value_t::object: + { + m_value.object->clear(); + break; + } + + default: + break; + } + } + + /*! + @brief add an object to an array + + Appends the given element @a val to the end of the JSON value. If the + function is called on a JSON null value, an empty array is created before + appending @a val. + + @param[in] val the value to add to the JSON array + + @throw type_error.308 when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON array. Note how the `null` value was silently + converted to a JSON array.,push_back} + + @since version 1.0.0 + */ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (move semantics) + m_value.array->push_back(std::move(val)); + // invalidate object + val.m_type = value_t::null; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array + m_value.array->push_back(val); + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + Inserts the given element @a val to the JSON object. If the function is + called on a JSON null value, an empty object is created before inserting + @a val. + + @param[in] val the value to add to the JSON object + + @throw type_error.308 when called on a type other than JSON object or + null; example: `"cannot use push_back() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON object. Note how the `null` value was silently + converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 + */ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (JSON_UNLIKELY(not(is_null() or is_object()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array + m_value.object->insert(val); + } + + /*! + @brief add an object to an object + @copydoc push_back(const typename object_t::value_type&) + */ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param[in] init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(initializer_list_t init) + { + if (is_object() and init.size() == 2 and (*init.begin())->is_string()) + { + basic_json&& key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(initializer_list_t) + */ + reference operator+=(initializer_list_t init) + { + push_back(init); + return *this; + } + + /*! + @brief add an object to an array + + Creates a JSON value from the passed parameters @a args to the end of the + JSON value. If the function is called on a JSON null value, an empty array + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @throw type_error.311 when called on a type other than JSON array or + null; example: `"cannot use emplace_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` can be used to add + elements to a JSON array. Note how the `null` value was silently converted + to a JSON array.,emplace_back} + + @since version 2.0.8 + */ + template + void emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) + m_value.array->emplace_back(std::forward(args)...); + } + + /*! + @brief add an object to an object if key does not exist + + Inserts a new element into a JSON object constructed in-place with the + given @a args if there is no element with the key in the container. If the + function is called on a JSON null value, an empty object is created before + appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return a pair consisting of an iterator to the inserted element, or the + already-existing element if no insertion happened, and a bool + denoting whether the insertion took place. + + @throw type_error.311 when called on a type other than JSON object or + null; example: `"cannot use emplace() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `emplace()` can be used to add elements + to a JSON object. Note how the `null` value was silently converted to a + JSON object. Further note how no value is added if there was already one + value stored with the same key.,emplace} + + @since version 2.0.8 + */ + template + std::pair emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_object()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + auto res = m_value.object->emplace(std::forward(args)...); + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; + } + + /*! + @brief inserts element + + Inserts element @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] val element to insert + @return iterator pointing to the inserted @a val. + + @throw type_error.309 if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Constant plus linear in the distance between @a pos and end of + the container. + + @liveexample{The example shows how `insert()` is used.,insert} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const basic_json& val) + { + // insert only works for arrays + if (JSON_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts element + @copydoc insert(const_iterator, const basic_json&) + */ + iterator insert(const_iterator pos, basic_json&& val) + { + return insert(pos, val); + } + + /*! + @brief inserts elements + + Inserts @a cnt copies of @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert + @return iterator pointing to the first element inserted, or @a pos if + `cnt==0` + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Linear in @a cnt plus linear in the distance between @a pos + and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__count} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + // insert only works for arrays + if (JSON_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)` before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + @throw invalid_iterator.211 if @a first or @a last are iterators into + container for which insert is called; example: `"passed iterators may not + belong to container"` + + @return iterator pointing to the first element inserted, or @a pos if + `first==last` + + @complexity Linear in `std::distance(first, last)` plus linear in the + distance between @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__range} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + // insert only works for arrays + if (JSON_UNLIKELY(not is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + if (JSON_UNLIKELY(first.m_object == this)) + { + JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert( + pos.m_it.array_iterator, + first.m_it.array_iterator, + last.m_it.array_iterator); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from initializer list @a ilist before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] ilist initializer list to insert the values from + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @return iterator pointing to the first element inserted, or @a pos if + `ilist` is empty + + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__ilist} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, initializer_list_t ilist) + { + // insert only works for arrays + if (JSON_UNLIKELY(not is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end()); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)`. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than objects; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number + of elements to insert. + + @liveexample{The example shows how `insert()` is used.,insert__range_object} + + @since version 3.0.0 + */ + void insert(const_iterator first, const_iterator last) + { + // insert only works for objects + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_UNLIKELY(not first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from JSON object @a j and overwrites existing keys. + + @param[in] j JSON object to read values from + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_reference j) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()))); + } + + for (auto it = j.cbegin(); it != j.cend(); ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from from range `[first, last)` and overwrites existing + keys. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used__range.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_iterator first, const_iterator last) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_UNLIKELY(not first.m_object->is_object() + or not first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + for (auto it = first; it != last; ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); + assert_invariant(); + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw type_error.310 when JSON value is not an array; example: `"cannot + use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how arrays can be swapped with + `swap()`.,swap__array_t} + + @since version 1.0.0 + */ + void swap(array_t& other) + { + // swap only works for arrays + if (JSON_LIKELY(is_array())) + { + std::swap(*(m_value.array), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON object with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other object to exchange the contents with + + @throw type_error.310 when JSON value is not an object; example: + `"cannot use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how objects can be swapped with + `swap()`.,swap__object_t} + + @since version 1.0.0 + */ + void swap(object_t& other) + { + // swap only works for objects + if (JSON_LIKELY(is_object())) + { + std::swap(*(m_value.object), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other string to exchange the contents with + + @throw type_error.310 when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__string_t} + + @since version 1.0.0 + */ + void swap(string_t& other) + { + // swap only works for strings + if (JSON_LIKELY(is_string())) + { + std::swap(*(m_value.string), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /// @} + + public: + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + /*! + @brief comparison: equal + + Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) they are from the same type and (2) + their stored values are the same according to their respective + `operator==`. + - Integer and floating-point numbers are automatically converted before + comparison. Note than two NaN values are always treated as unequal. + - Two JSON null values are equal. + + @note Floating-point inside JSON values numbers are compared with + `json::number_float_t::operator==` which is `double::operator==` by + default. To compare floating-point while respecting an epsilon, an alternative + [comparison function](https://github.com/mariokonrad/marnav/blob/master/src/marnav/math/floatingpoint.hpp#L34-#L39) + could be used, for instance + @code {.cpp} + template::value, T>::type> + inline bool is_same(T a, T b, T epsilon = std::numeric_limits::epsilon()) noexcept + { + return std::abs(a - b) <= epsilon; + } + @endcode + + @note NaN values never compare equal to themselves or to other NaN values. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are equal + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__equal} + + @since version 1.0.0 + */ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + return (*lhs.m_value.array == *rhs.m_value.array); + + case value_t::object: + return (*lhs.m_value.object == *rhs.m_value.object); + + case value_t::null: + return true; + + case value_t::string: + return (*lhs.m_value.string == *rhs.m_value.string); + + case value_t::boolean: + return (lhs.m_value.boolean == rhs.m_value.boolean); + + case value_t::number_integer: + return (lhs.m_value.number_integer == rhs.m_value.number_integer); + + case value_t::number_unsigned: + return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned); + + case value_t::number_float: + return (lhs.m_value.number_float == rhs.m_value.number_float); + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return (static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float); + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return (lhs.m_value.number_float == static_cast(rhs.m_value.number_integer)); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return (static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float); + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return (lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned)); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return (static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return (lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned)); + } + + return false; + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs == basic_json(rhs)); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) == rhs); + } + + /*! + @brief comparison: not equal + + Compares two JSON values for inequality by calculating `not (lhs == rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are not equal + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__notequal} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs == rhs); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs != basic_json(rhs)); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) != rhs); + } + + /*! + @brief comparison: less than + + Compares whether one JSON value @a lhs is less than another JSON value @a + rhs according to the following rules: + - If @a lhs and @a rhs have the same type, the values are compared using + the default `<` operator. + - Integer and floating-point numbers are automatically converted before + comparison + - In case @a lhs and @a rhs have different types, the values are ignored + and the order of the types is considered, see + @ref operator<(const value_t, const value_t). + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__less} + + @since version 1.0.0 + */ + friend bool operator<(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + return (*lhs.m_value.array) < (*rhs.m_value.array); + + case value_t::object: + return *lhs.m_value.object < *rhs.m_value.object; + + case value_t::null: + return false; + + case value_t::string: + return *lhs.m_value.string < *rhs.m_value.string; + + case value_t::boolean: + return lhs.m_value.boolean < rhs.m_value.boolean; + + case value_t::number_integer: + return lhs.m_value.number_integer < rhs.m_value.number_integer; + + case value_t::number_unsigned: + return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; + + case value_t::number_float: + return lhs.m_value.number_float < rhs.m_value.number_float; + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs < basic_json(rhs)); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) < rhs); + } + + /*! + @brief comparison: less than or equal + + Compares whether one JSON value @a lhs is less than or equal to another + JSON value by calculating `not (rhs < lhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greater} + + @since version 1.0.0 + */ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + return not (rhs < lhs); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs <= basic_json(rhs)); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) <= rhs); + } + + /*! + @brief comparison: greater than + + Compares whether one JSON value @a lhs is greater than another + JSON value by calculating `not (lhs <= rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__lessequal} + + @since version 1.0.0 + */ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs <= rhs); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs > basic_json(rhs)); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) > rhs); + } + + /*! + @brief comparison: greater than or equal + + Compares whether one JSON value @a lhs is greater than or equal to another + JSON value by calculating `not (lhs < rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greaterequal} + + @since version 1.0.0 + */ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs < rhs); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs >= basic_json(rhs)); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) >= rhs); + } + + /// @} + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ + + /*! + @brief serialize to stream + + Serialize the given JSON value @a j to the output stream @a o. The JSON + value will be serialized using the @ref dump member function. + + - The indentation of the output can be controlled with the member variable + `width` of the output stream @a o. For instance, using the manipulator + `std::setw(4)` on @a o sets the indentation level to `4` and the + serialization result is the same as calling `dump(4)`. + + - The indentation character can be controlled with the member variable + `fill` of the output stream @a o. For instance, the manipulator + `std::setfill('\\t')` sets indentation to use a tab character rather than + the default space character. + + @param[in,out] o stream to serialize to + @param[in] j JSON value to serialize + + @return the stream @a o + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + + @complexity Linear. + + @liveexample{The example below shows the serialization with different + parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0; indentation character added in version 3.0.0 + */ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as indentation parameter if nonzero + const bool pretty_print = (o.width() > 0); + const auto indentation = (pretty_print ? o.width() : 0); + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // do the actual serialization + serializer s(detail::output_adapter(o), o.fill()); + s.dump(j, pretty_print, false, static_cast(indentation)); + return o; + } + + /*! + @brief serialize to stream + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use + @ref operator<<(std::ostream&, const basic_json&) + instead; that is, replace calls like `j >> o;` with `o << j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_DEPRECATED + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } + + /// @} + + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /*! + @brief deserialize from a compatible input + + This function reads from a compatible input. Examples are: + - an array of 1-byte values + - strings with character/literal type with size of 1 byte + - input streams + - container with contiguous storage of 1-byte values. Compatible container + types include `std::vector`, `std::string`, `std::array`, + `std::valarray`, and `std::initializer_list`. Furthermore, C-style + arrays can be used with `std::begin()`/`std::end()`. User-defined + containers can be used as long as they implement random-access iterators + and a contiguous storage. + + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @pre The container storage is contiguous. Violating this precondition + yields undefined behavior. **This precondition is enforced with an + assertion.** + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with a noncompliant container and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @param[in] i input to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an array.,parse__array__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function reading + from a contiguous container.,parse__contiguouscontainer__parser_callback_t} + + @since version 2.0.3 (contiguous containers) + */ + static basic_json parse(detail::input_adapter i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } + + /*! + @copydoc basic_json parse(detail::input_adapter, const parser_callback_t) + */ + static basic_json parse(detail::input_adapter& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } + + static bool accept(detail::input_adapter i) + { + return parser(i).accept(true); + } + + static bool accept(detail::input_adapter& i) + { + return parser(i).accept(true); + } + + /*! + @brief deserialize from an iterator range with contiguous storage + + This function reads from an iterator range of a container with contiguous + storage of 1-byte values. Compatible container types include + `std::vector`, `std::string`, `std::array`, `std::valarray`, and + `std::initializer_list`. Furthermore, C-style arrays can be used with + `std::begin()`/`std::end()`. User-defined containers can be used as long + as they implement random-access iterators and a contiguous storage. + + @pre The iterator range is contiguous. Violating this precondition yields + undefined behavior. **This precondition is enforced with an assertion.** + @pre Each element in the range has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with noncompliant iterators and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @tparam IteratorType iterator of container with contiguous storage + @param[in] first begin of the range to parse (included) + @param[in] last end of the range to parse (excluded) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return result of the deserialization + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an iterator range.,parse__iteratortype__parser_callback_t} + + @since version 2.0.3 + */ + template::iterator_category>::value, int>::type = 0> + static basic_json parse(IteratorType first, IteratorType last, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result); + return result; + } + + template::iterator_category>::value, int>::type = 0> + static bool accept(IteratorType first, IteratorType last) + { + return parser(detail::input_adapter(first, last)).accept(true); + } + + /*! + @brief deserialize from stream + @deprecated This stream operator is deprecated and will be removed in + version 4.0.0 of the library. Please use + @ref operator>>(std::istream&, basic_json&) + instead; that is, replace calls like `j << i;` with `i >> j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_DEPRECATED + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + return operator>>(i, j); + } + + /*! + @brief deserialize from stream + + Deserializes an input stream to a JSON value. + + @param[in,out] i input stream to read a serialized JSON value from + @param[in,out] j JSON value to write the deserialized input to + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below shows how a JSON value is constructed by + reading a serialization from a stream.,operator_deserialize} + + @sa parse(std::istream&, const parser_callback_t) for a variant with a + parser callback function to filter values while parsing + + @since version 1.0.0 + */ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + parser(detail::input_adapter(i)).parse(false, j); + return i; + } + + /// @} + + /////////////////////////// + // convenience functions // + /////////////////////////// + + /*! + @brief return the type as string + + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. + + @return a string representation of a the @a m_type member: + Value type | return value + ----------- | ------------- + null | `"null"` + boolean | `"boolean"` + string | `"string"` + number | `"number"` (for all number types) + object | `"object"` + array | `"array"` + discarded | `"discarded"` + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Constant. + + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,type_name} + + @sa @ref type() -- return the type of the JSON value + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + + @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` + since 3.0.0 + */ + const char* type_name() const noexcept + { + { + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + } + + + private: + ////////////////////// + // member variables // + ////////////////////// + + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + public: + /*! + @brief create a CBOR serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the CBOR (Concise + Binary Object Representation) serialization format. CBOR is a binary + serialization format which aims to be more compact than JSON itself, yet + more efficient to parse. + + The library uses the following mapping from JSON values types to + CBOR types according to the CBOR specification (RFC 7049): + + JSON value type | value/range | CBOR type | first byte + --------------- | ------------------------------------------ | ---------------------------------- | --------------- + null | `null` | Null | 0xF6 + boolean | `true` | True | 0xF5 + boolean | `false` | False | 0xF4 + number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B + number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A + number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 + number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 + number_integer | -24..-1 | Negative integer | 0x20..0x37 + number_integer | 0..23 | Integer | 0x00..0x17 + number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_unsigned | 0..23 | Integer | 0x00..0x17 + number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_float | *any value* | Double-Precision Float | 0xFB + string | *length*: 0..23 | UTF-8 string | 0x60..0x77 + string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 + string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 + string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A + string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B + array | *size*: 0..23 | array | 0x80..0x97 + array | *size*: 23..255 | array (1 byte follow) | 0x98 + array | *size*: 256..65535 | array (2 bytes follow) | 0x99 + array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A + array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B + object | *size*: 0..23 | map | 0xA0..0xB7 + object | *size*: 23..255 | map (1 byte follow) | 0xB8 + object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 + object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA + object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a CBOR value. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The following CBOR types are not used in the conversion: + - byte strings (0x40..0x5F) + - UTF-8 strings terminated by "break" (0x7F) + - arrays terminated by "break" (0x9F) + - maps terminated by "break" (0xBF) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - tagged items (0xC6..0xD4, 0xD8..0xDB) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + - half and single-precision floats (0xF9-0xFA) + - break (0xFF) + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in CBOR format.,to_cbor} + + @sa http://cbor.io + @sa @ref from_cbor(detail::input_adapter, const bool strict) for the + analogous deserialization + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9 + */ + static std::vector to_cbor(const basic_json& j) + { + std::vector result; + to_cbor(j, result); + return result; + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the MessagePack + serialization format. MessagePack is a binary serialization format which + aims to be more compact than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + MessagePack types according to the MessagePack specification: + + JSON value type | value/range | MessagePack type | first byte + --------------- | --------------------------------- | ---------------- | ---------- + null | `null` | nil | 0xC0 + boolean | `true` | true | 0xC3 + boolean | `false` | false | 0xC2 + number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3 + number_integer | -2147483648..-32769 | int32 | 0xD2 + number_integer | -32768..-129 | int16 | 0xD1 + number_integer | -128..-33 | int8 | 0xD0 + number_integer | -32..-1 | negative fixint | 0xE0..0xFF + number_integer | 0..127 | positive fixint | 0x00..0x7F + number_integer | 128..255 | uint 8 | 0xCC + number_integer | 256..65535 | uint 16 | 0xCD + number_integer | 65536..4294967295 | uint 32 | 0xCE + number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_unsigned | 0..127 | positive fixint | 0x00..0x7F + number_unsigned | 128..255 | uint 8 | 0xCC + number_unsigned | 256..65535 | uint 16 | 0xCD + number_unsigned | 65536..4294967295 | uint 32 | 0xCE + number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_float | *any value* | float 64 | 0xCB + string | *length*: 0..31 | fixstr | 0xA0..0xBF + string | *length*: 32..255 | str 8 | 0xD9 + string | *length*: 256..65535 | str 16 | 0xDA + string | *length*: 65536..4294967295 | str 32 | 0xDB + array | *size*: 0..15 | fixarray | 0x90..0x9F + array | *size*: 16..65535 | array 16 | 0xDC + array | *size*: 65536..4294967295 | array 32 | 0xDD + object | *size*: 0..15 | fix map | 0x80..0x8F + object | *size*: 16..65535 | map 16 | 0xDE + object | *size*: 65536..4294967295 | map 32 | 0xDF + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a MessagePack value. + + @note The following values can **not** be converted to a MessagePack value: + - strings with more than 4294967295 bytes + - arrays with more than 4294967295 elements + - objects with more than 4294967295 elements + + @note The following MessagePack types are not used in the conversion: + - bin 8 - bin 32 (0xC4..0xC6) + - ext 8 - ext 32 (0xC7..0xC9) + - float 32 (0xCA) + - fixext 1 - fixext 16 (0xD4..0xD8) + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in MessagePack format.,to_msgpack} + + @sa http://msgpack.org + @sa @ref from_msgpack(const std::vector&, const size_t) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9 + */ + static std::vector to_msgpack(const basic_json& j) + { + std::vector result; + to_msgpack(j, result); + return result; + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + /*! + @brief create a UBJSON serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the UBJSON + (Universal Binary JSON) serialization format. UBJSON aims to be more compact + than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + UBJSON types according to the UBJSON specification: + + JSON value type | value/range | UBJSON type | marker + --------------- | --------------------------------- | ----------- | ------ + null | `null` | null | `Z` + boolean | `true` | true | `T` + boolean | `false` | false | `F` + number_integer | -9223372036854775808..-2147483649 | int64 | `L` + number_integer | -2147483648..-32769 | int32 | `l` + number_integer | -32768..-129 | int16 | `I` + number_integer | -128..127 | int8 | `i` + number_integer | 128..255 | uint8 | `U` + number_integer | 256..32767 | int16 | `I` + number_integer | 32768..2147483647 | int32 | `l` + number_integer | 2147483648..9223372036854775807 | int64 | `L` + number_unsigned | 0..127 | int8 | `i` + number_unsigned | 128..255 | uint8 | `U` + number_unsigned | 256..32767 | int16 | `I` + number_unsigned | 32768..2147483647 | int32 | `l` + number_unsigned | 2147483648..9223372036854775807 | int64 | `L` + number_float | *any value* | float64 | `D` + string | *with shortest length indicator* | string | `S` + array | *see notes on optimized format* | array | `[` + object | *see notes on optimized format* | map | `{` + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a UBJSON value. + + @note The following values can **not** be converted to a UBJSON value: + - strings with more than 9223372036854775807 bytes (theoretical) + - unsigned integer numbers above 9223372036854775807 + + @note The following markers are not used in the conversion: + - `Z`: no-op values are not created. + - `C`: single-byte strings are serialized with `S` markers. + + @note Any UBJSON output created @ref to_ubjson can be successfully parsed + by @ref from_ubjson. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The optimized formats for containers are supported: Parameter + @a use_size adds size information to the beginning of a container and + removes the closing marker. Parameter @a use_type further checks + whether all elements of a container have the same type and adds the + type marker to the beginning of the container. The @a use_type + parameter must only be used together with @a use_size = true. Note + that @a use_size = true alone may result in larger representations - + the benefit of this parameter is that the receiving side is + immediately informed on the number of elements of the container. + + @param[in] j JSON value to serialize + @param[in] use_size whether to add size annotations to container types + @param[in] use_type whether to add type annotations to container types + (must be combined with @a use_size = true) + @return UBJSON serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in UBJSON format.,to_ubjson} + + @sa http://ubjson.org + @sa @ref from_ubjson(detail::input_adapter, const bool strict) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + + @since version 3.1.0 + */ + static std::vector to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector result; + to_ubjson(j, result, use_size, use_type); + return result; + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + /*! + @brief create a JSON value from an input in CBOR format + + Deserializes a given input @a i to a JSON value using the CBOR (Concise + Binary Object Representation) serialization format. + + The library maps CBOR types to JSON value types as follows: + + CBOR type | JSON value type | first byte + ---------------------- | --------------- | ---------- + Integer | number_unsigned | 0x00..0x17 + Unsigned integer | number_unsigned | 0x18 + Unsigned integer | number_unsigned | 0x19 + Unsigned integer | number_unsigned | 0x1A + Unsigned integer | number_unsigned | 0x1B + Negative integer | number_integer | 0x20..0x37 + Negative integer | number_integer | 0x38 + Negative integer | number_integer | 0x39 + Negative integer | number_integer | 0x3A + Negative integer | number_integer | 0x3B + Negative integer | number_integer | 0x40..0x57 + UTF-8 string | string | 0x60..0x77 + UTF-8 string | string | 0x78 + UTF-8 string | string | 0x79 + UTF-8 string | string | 0x7A + UTF-8 string | string | 0x7B + UTF-8 string | string | 0x7F + array | array | 0x80..0x97 + array | array | 0x98 + array | array | 0x99 + array | array | 0x9A + array | array | 0x9B + array | array | 0x9F + map | object | 0xA0..0xB7 + map | object | 0xB8 + map | object | 0xB9 + map | object | 0xBA + map | object | 0xBB + map | object | 0xBF + False | `false` | 0xF4 + True | `true` | 0xF5 + Nill | `null` | 0xF6 + Half-Precision Float | number_float | 0xF9 + Single-Precision Float | number_float | 0xFA + Double-Precision Float | number_float | 0xFB + + @warning The mapping is **incomplete** in the sense that not all CBOR + types can be converted to a JSON value. The following CBOR types + are not supported and will yield parse errors (parse_error.112): + - byte strings (0x40..0x5F) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - tagged items (0xC6..0xD4, 0xD8..0xDB) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + + @warning CBOR allows map keys of any type, whereas JSON only allows + strings as keys in object values. Therefore, CBOR maps with keys + other than UTF-8 strings are rejected (parse_error.113). + + @note Any CBOR output created @ref to_cbor can be successfully parsed by + @ref from_cbor. + + @param[in] i an input in CBOR format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @return deserialized JSON value + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from CBOR were + used in the given input @a v or if the input is not valid CBOR + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} + + @sa http://cbor.io + @sa @ref to_cbor(const basic_json&) for the analogous serialization + @sa @ref from_msgpack(detail::input_adapter, const bool) for the + related MessagePack format + @sa @ref from_ubjson(detail::input_adapter, const bool) for the related + UBJSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 + */ + static basic_json from_cbor(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_cbor(strict); + } + + /*! + @copydoc from_cbor(detail::input_adapter, const bool) + */ + template::value, int> = 0> + static basic_json from_cbor(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_cbor(strict); + } + + /*! + @brief create a JSON value from an input in MessagePack format + + Deserializes a given input @a i to a JSON value using the MessagePack + serialization format. + + The library maps MessagePack types to JSON value types as follows: + + MessagePack type | JSON value type | first byte + ---------------- | --------------- | ---------- + positive fixint | number_unsigned | 0x00..0x7F + fixmap | object | 0x80..0x8F + fixarray | array | 0x90..0x9F + fixstr | string | 0xA0..0xBF + nil | `null` | 0xC0 + false | `false` | 0xC2 + true | `true` | 0xC3 + float 32 | number_float | 0xCA + float 64 | number_float | 0xCB + uint 8 | number_unsigned | 0xCC + uint 16 | number_unsigned | 0xCD + uint 32 | number_unsigned | 0xCE + uint 64 | number_unsigned | 0xCF + int 8 | number_integer | 0xD0 + int 16 | number_integer | 0xD1 + int 32 | number_integer | 0xD2 + int 64 | number_integer | 0xD3 + str 8 | string | 0xD9 + str 16 | string | 0xDA + str 32 | string | 0xDB + array 16 | array | 0xDC + array 32 | array | 0xDD + map 16 | object | 0xDE + map 32 | object | 0xDF + negative fixint | number_integer | 0xE0-0xFF + + @warning The mapping is **incomplete** in the sense that not all + MessagePack types can be converted to a JSON value. The following + MessagePack types are not supported and will yield parse errors: + - bin 8 - bin 32 (0xC4..0xC6) + - ext 8 - ext 32 (0xC7..0xC9) + - fixext 1 - fixext 16 (0xD4..0xD8) + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @param[in] i an input in MessagePack format convertible to an input + adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from MessagePack were + used in the given input @a i or if the input is not valid MessagePack + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + MessagePack format to a JSON value.,from_msgpack} + + @sa http://msgpack.org + @sa @ref to_msgpack(const basic_json&) for the analogous serialization + @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR + format + @sa @ref from_ubjson(detail::input_adapter, const bool) for the related + UBJSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 + */ + static basic_json from_msgpack(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_msgpack(strict); + } + + /*! + @copydoc from_msgpack(detail::input_adapter, const bool) + */ + template::value, int> = 0> + static basic_json from_msgpack(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_msgpack(strict); + } + + /*! + @brief create a JSON value from an input in UBJSON format + + Deserializes a given input @a i to a JSON value using the UBJSON (Universal + Binary JSON) serialization format. + + The library maps UBJSON types to JSON value types as follows: + + UBJSON type | JSON value type | marker + ----------- | --------------------------------------- | ------ + no-op | *no value, next value is read* | `N` + null | `null` | `Z` + false | `false` | `F` + true | `true` | `T` + float32 | number_float | `d` + float64 | number_float | `D` + uint8 | number_unsigned | `U` + int8 | number_integer | `i` + int16 | number_integer | `I` + int32 | number_integer | `l` + int64 | number_integer | `L` + string | string | `S` + char | string | `C` + array | array (optimized values are supported) | `[` + object | object (optimized values are supported) | `{` + + @note The mapping is **complete** in the sense that any UBJSON value can + be converted to a JSON value. + + @param[in] i an input in UBJSON format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if a parse error occurs + @throw parse_error.113 if a string could not be parsed successfully + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + UBJSON format to a JSON value.,from_ubjson} + + @sa http://ubjson.org + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + analogous serialization + @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR + format + @sa @ref from_msgpack(detail::input_adapter, const bool) for the related + MessagePack format + + @since version 3.1.0 + */ + static basic_json from_ubjson(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_ubjson(strict); + } + + template::value, int> = 0> + static basic_json from_ubjson(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_ubjson(strict); + } + + /// @} + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer} + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitive values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this function, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw parse_error.104 if the JSON patch does not consist of an array of + objects + + @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @throw out_of_range.401 if an array index is out of range. + + @throw out_of_range.403 if a JSON pointer inside the patch could not be + resolved successfully in the current JSON value; example: `"key baz not + found"` + + @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", + "move") + + @throw other_error.501 if "test" operation was unsuccessful + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string & op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.is_root()) + { + result = val; + } + else + { + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = json_pointer::array_index(last_path); + if (JSON_UNLIKELY(static_cast(idx) > parent.size())) + { + // avoid undefined behavior + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + else + { + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + } + break; + } + + default: + { + // if there exists a parent it cannot be primitive + assert(false); // LCOV_EXCL_LINE + } + } + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (JSON_LIKELY(it != parent.end())) + { + parent.erase(it); + } + else + { + JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found")); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(static_cast(json_pointer::array_index(last_path))); + } + }; + + // type check: top level value must be an array + if (JSON_UNLIKELY(not json_patch.is_array())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // iterate and apply the operations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json & + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (JSON_UNLIKELY(it == val.m_value.object->end())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'")); + } + + // check if result is of type string + if (JSON_UNLIKELY(string_type and not it->second.is_string())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'")); + } + + // no error: return value + return it->second; + }; + + // type check: every element of the array must be an object + if (JSON_UNLIKELY(not val.is_object())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // collect mandatory members + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const std::string from_path = get_value("copy", "from", true); + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The copy is functionally identical to an "add" + // operation at the target location using the value + // specified in the "from" member. + operation_add(ptr, v); + break; + } + + case patch_operations::test: + { + bool success = false; + JSON_TRY + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_CATCH (out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (JSON_UNLIKELY(not success)) + { + JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump())); + } + + break; + } + + case patch_operations::invalid: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid")); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to compare from + @param[in] target JSON value to compare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + @sa @ref merge_patch -- apply a JSON Merge Patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + static basic_json diff(const basic_json& source, const basic_json& target, + const std::string& path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + } + else + { + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + std::size_t i = 0; + while (i < source.size() and i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + std::to_string(i)}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.cbegin(); it != source.cend(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.cbegin(); it != target.cend(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + break; + } + } + } + + return result; + } + + /// @} + + //////////////////////////////// + // JSON Merge Patch functions // + //////////////////////////////// + + /// @name JSON Merge Patch functions + /// @{ + + /*! + @brief applies a JSON Merge Patch + + The merge patch format is primarily intended for use with the HTTP PATCH + method as a means of describing a set of modifications to a target + resource's content. This function applies a merge patch to the current + JSON value. + + The function implements the following algorithm from Section 2 of + [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396): + + ``` + define MergePatch(Target, Patch): + if Patch is an Object: + if Target is not an Object: + Target = {} // Ignore the contents and set it to an empty Object + for each Name/Value pair in Patch: + if Value is null: + if Name exists in Target: + remove the Name/Value pair from Target + else: + Target[Name] = MergePatch(Target[Name], Value) + return Target + else: + return Patch + ``` + + Thereby, `Target` is the current object; that is, the patch is applied to + the current value. + + @param[in] patch the patch to apply + + @complexity Linear in the lengths of @a patch. + + @liveexample{The following code shows how a JSON Merge Patch is applied to + a JSON document.,merge_patch} + + @sa @ref patch -- apply a JSON patch + @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) + + @since version 3.0.0 + */ + void merge_patch(const basic_json& patch) + { + if (patch.is_object()) + { + if (not is_object()) + { + *this = object(); + } + for (auto it = patch.begin(); it != patch.end(); ++it) + { + if (it.value().is_null()) + { + erase(it.key()); + } + else + { + operator[](it.key()).merge_patch(it.value()); + } + } + } + else + { + *this = patch; + } + } + + /// @} +}; +} // namespace nlohmann + +/////////////////////// +// nonmember support // +/////////////////////// + +// specialization of std::swap, and std::hash +namespace std +{ +/*! +@brief exchanges the values of two JSON objects + +@since version 1.0.0 +*/ +template<> +inline void swap(nlohmann::json& j1, + nlohmann::json& j2) noexcept( + is_nothrow_move_constructible::value and + is_nothrow_move_assignable::value + ) +{ + j1.swap(j2); +} + +/// hash value for JSON objects +template<> +struct hash +{ + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ + std::size_t operator()(const nlohmann::json& j) const + { + // a naive hashing via the string representation + const auto& h = hash(); + return h(j.dump()); + } +}; + +/// specialization for std::less +/// @note: do not remove the space after '<', +/// see https://github.com/nlohmann/json/pull/679 +template<> +struct less< ::nlohmann::detail::value_t> +{ + /*! + @brief compare two value_t enum values + @since version 3.0.0 + */ + bool operator()(nlohmann::detail::value_t lhs, + nlohmann::detail::value_t rhs) const noexcept + { + return nlohmann::detail::operator<(lhs, rhs); + } +}; + +} // namespace std + +/*! +@brief user-defined string literal for JSON values + +This operator implements a user-defined string literal for JSON objects. It +can be used by adding `"_json"` to a string literal and returns a JSON object +if no parse error occurred. + +@param[in] s a string representation of a JSON object +@param[in] n the length of string @a s +@return a JSON object + +@since version 1.0.0 +*/ +inline nlohmann::json operator "" _json(const char* s, std::size_t n) +{ + return nlohmann::json::parse(s, s + n); +} + +/*! +@brief user-defined string literal for JSON pointer + +This operator implements a user-defined string literal for JSON Pointers. It +can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer +object if no parse error occurred. + +@param[in] s a string representation of a JSON Pointer +@param[in] n the length of string @a s +@return a JSON pointer object + +@since version 2.0.0 +*/ +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) +{ + return nlohmann::json::json_pointer(std::string(s, n)); +} + +// #include + + +// restore GCC/clang diagnostic settings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic pop +#endif +#if defined(__clang__) + #pragma GCC diagnostic pop +#endif + +// clean up +#undef JSON_CATCH +#undef JSON_THROW +#undef JSON_TRY +#undef JSON_LIKELY +#undef JSON_UNLIKELY +#undef JSON_DEPRECATED +#undef JSON_HAS_CPP_14 +#undef JSON_HAS_CPP_17 +#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION +#undef NLOHMANN_BASIC_JSON_TPL +#undef NLOHMANN_JSON_HAS_HELPER + + +#endif diff --git a/paperio/dockers/elixir/Dockerfile b/paperio/dockers/elixir/Dockerfile new file mode 100644 index 0000000..1d132a8 --- /dev/null +++ b/paperio/dockers/elixir/Dockerfile @@ -0,0 +1,25 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +LABEL authors="Alexey Bolshakov , Sergey Samokhvalov " + +COPY mix.exs /opt/client/default/mix.exs +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 +ENV ERL_AFLAGS="+A 1 +S 2:2 +SDcpu 2:2" +ENV SOLUTION_CODE_PATH=/opt/client/solution/lib + +RUN (echo 'deb http://packages.erlang-solutions.com/ubuntu xenial contrib' >> /etc/apt/sources.list) \ + && (apt-key adv --fetch-keys http://packages.erlang-solutions.com/ubuntu/erlang_solutions.asc) \ + && apt-get update \ + && apt-get install -y wget elixir \ + && cd /opt/client/default \ + && mix local.hex --force \ + && mix deps.get \ + && mkdir -p $SOLUTION_CODE_PATH + + +ENV COMPILED_FILE_PATH=/opt/client/strategy +ENV SOLUTION_CODE_ENTRYPOINT=strategy.ex + +ENV MIX_ENV=prod +ENV COMPILATION_COMMAND='cd /opt/client/solution/ && if [ ! -f "mix.exs" ]; then cp -r ../default/* .; fi && mix escript.build 2>&1' +ENV RUN_COMMAND='/usr/bin/escript "$MOUNT_POINT"' diff --git a/paperio/dockers/elixir/mix.exs b/paperio/dockers/elixir/mix.exs new file mode 100644 index 0000000..ea676fb --- /dev/null +++ b/paperio/dockers/elixir/mix.exs @@ -0,0 +1,17 @@ +defmodule Strategy.Mixfile do + use Mix.Project + + @output_filename if System.get_env("COMPILED_FILE_PATH"), do: System.get_env("COMPILED_FILE_PATH"), else: "strategy" + + def project do + [app: :elixir_strategy, + version: "0.1.0", + elixir: "~> 1.1", + escript: [main_module: Strategy, path: @output_filename, emu_args: "+A 4 +S 4:4 +SDcpu 4:4"], + deps: deps()] + end + + defp deps do + [{:poison, "~> 3.1"}] + end +end diff --git a/paperio/dockers/golang/Dockerfile b/paperio/dockers/golang/Dockerfile new file mode 100644 index 0000000..8ed62f3 --- /dev/null +++ b/paperio/dockers/golang/Dockerfile @@ -0,0 +1,17 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Boris Kolganov + +RUN apt-get update -y && apt-get install --no-install-recommends -y -q curl build-essential ca-certificates git mercurial bzr +RUN mkdir /goroot && curl https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz | tar xvzf - -C /goroot --strip-components=1 +RUN mkdir /gopath + +ENV GOROOT /goroot +ENV GOPATH /gopath +ENV PATH $PATH:$GOROOT/bin:$GOPATH/bin + +RUN go install -buildmode=shared std + +ENV COMPILED_FILE_PATH=/opt/client/main +ENV SOLUTION_CODE_ENTRYPOINT=main.go +ENV COMPILATION_COMMAND='go build -linkshared $SOLUTION_CODE_PATH/main.go 2>&1 > /dev/null' +ENV RUN_COMMAND='/lib64/ld-linux-x86-64.so.2 $MOUNT_POINT' diff --git a/paperio/dockers/haskell/Dockerfile b/paperio/dockers/haskell/Dockerfile new file mode 100644 index 0000000..50129bb --- /dev/null +++ b/paperio/dockers/haskell/Dockerfile @@ -0,0 +1,17 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Cthulhu + +ENV PATH=/opt/ghc/bin:$PATH + +RUN apt-get update -qq && apt-get install -y software-properties-common && \ + add-apt-repository -y ppa:hvr/ghc && \ + apt-get update -qq && apt-get install -y ghc-8.4.1 cabal-install-2.2 && \ + cabal update && cabal install -j aeson classy-prelude conduit && \ + rm -rf /root/.cabal/packages # takes too much space, and we don't need it anymore + +ENV COMPILED_FILE_PATH=/opt/client/a.out +ENV SOLUTION_CODE_ENTRYPOINT=Main.hs +ENV SOLUTION_CODE_PATH=/opt/client/solution + +ENV COMPILATION_COMMAND='ghc -O2 -Wall -i$SOLUTION_CODE_PATH -o $COMPILED_FILE_PATH $SOLUTION_CODE_PATH/$SOLUTION_CODE_ENTRYPOINT 2>&1 > /dev/null' +ENV RUN_COMMAND='/lib64/ld-linux-x86-64.so.2 $MOUNT_POINT' diff --git a/paperio/dockers/java1.8/Dockerfile b/paperio/dockers/java1.8/Dockerfile new file mode 100644 index 0000000..40b9954 --- /dev/null +++ b/paperio/dockers/java1.8/Dockerfile @@ -0,0 +1,22 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Boris Kolganov +RUN apt-get update -y && \ + apt-get install -y maven software-properties-common curl && \ + echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | debconf-set-selections && \ + add-apt-repository -y ppa:webupd8team/java && \ + apt-get update -y && \ + apt-get install -y oracle-java8-installer python && \ + rm -rf /var/lib/apt/lists/* && \ + rm -rf /var/cache/oracle-jdk8-installer && \ + chmod a+rx /root + +ENV SOLUTION_CODE_ENTRYPOINT=Main.java +ENV COMPILED_FILE_PATH=/opt/client/javaStrategy.jar +ENV SOLUTION_CODE_PATH=/opt/client/src/main/java/ +ENV COMPILATION_COMMAND='mvn package -q' +ENV RUN_COMMAND='java -jar $MOUNT_POINT' + +COPY pom.xml ./ +RUN mkdir -p src/main/java && mvn dependency:go-offline && \ + mvn package && \ + rm -rf javaStrategy.jar target/classes/ diff --git a/paperio/dockers/java1.8/pom.xml b/paperio/dockers/java1.8/pom.xml new file mode 100644 index 0000000..9802c3f --- /dev/null +++ b/paperio/dockers/java1.8/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + JavaStrategy + JavaStrategy + 1.0 + + + + UTF-8 + 1.8 + 1.8 + + + + javaStrategy + + + maven-jar-plugin + 2.3 + + ${basedir} + + + Main + true + ${settings.localRepository} + repository + + + + + + + + + + org.json + json + 20180130 + + + + com.google.code.gson + gson + 2.8.5 + + + + diff --git a/paperio/dockers/java1.9/Dockerfile b/paperio/dockers/java1.9/Dockerfile new file mode 100644 index 0000000..5d09329 --- /dev/null +++ b/paperio/dockers/java1.9/Dockerfile @@ -0,0 +1,16 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Boris Kolganov +RUN apt-get update -y && \ + apt-get install -y maven && \ + apt-get -o Dpkg::Options::="--force-overwrite" install -y openjdk-9-jdk + +ENV SOLUTION_CODE_ENTRYPOINT=Main.java +ENV COMPILED_FILE_PATH=/opt/client/javaStrategy.jar +ENV SOLUTION_CODE_PATH=/opt/client/src/main/java/ +ENV COMPILATION_COMMAND='mvn package -q' +ENV RUN_COMMAND='java -jar $MOUNT_POINT' + +COPY pom.xml ./ +RUN mkdir -p src/main/java && mvn dependency:go-offline && \ + mvn package && \ + rm -rf javaStrategy.jar target/classes/ diff --git a/paperio/dockers/java1.9/pom.xml b/paperio/dockers/java1.9/pom.xml new file mode 100644 index 0000000..31c46a6 --- /dev/null +++ b/paperio/dockers/java1.9/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + JavaStrategy + JavaStrategy + 1.0 + + + + UTF-8 + 1.9 + 1.9 + + + + javaStrategy + + + maven-jar-plugin + 2.3 + + ${basedir} + + + Main + true + ${settings.localRepository} + repository + + + + + + + + + + org.json + json + 20180130 + + + + com.google.code.gson + gson + 2.8.5 + + + + diff --git a/paperio/dockers/kotlin/Dockerfile b/paperio/dockers/kotlin/Dockerfile new file mode 100644 index 0000000..7aa8977 --- /dev/null +++ b/paperio/dockers/kotlin/Dockerfile @@ -0,0 +1,18 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Evgeniy Zuykin +RUN apt-get update -y && \ + apt-get install -y software-properties-common && \ + add-apt-repository -y ppa:cwchien/gradle && \ + apt-get update -y && \ + apt-get install -y openjdk-8-jdk gradle-3.5.1 && \ + rm -rf /var/lib/apt/lists/* + +ENV SOLUTION_CODE_ENTRYPOINT=Main.kt +ENV COMPILED_FILE_PATH=/opt/client/build/libs/kotlinStrategy.jar +ENV SOLUTION_CODE_PATH=/opt/client/src/main/kotlin/ +ENV COMPILATION_COMMAND='gradle build -q 2>&1' +ENV RUN_COMMAND='java -jar $MOUNT_POINT' + +COPY build.gradle ./ +RUN mkdir -p src/main/kotlin && gradle build -q && \ + rm -rf build diff --git a/paperio/dockers/kotlin/build.gradle b/paperio/dockers/kotlin/build.gradle new file mode 100644 index 0000000..a074fc8 --- /dev/null +++ b/paperio/dockers/kotlin/build.gradle @@ -0,0 +1,47 @@ +group 'KotlinStrategy' +version '1.0' + +buildscript { + ext.kotlin_version = '1.2.30' + + repositories { + maven { + url "https://plugins.gradle.org/m2/" + } + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.3' + } +} + +apply plugin: 'kotlin' +apply plugin: 'com.github.johnrengelman.shadow' + +repositories { + mavenCentral() +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" + compile 'org.json:json:20180130' +} + +sourceSets { + main.java.srcDirs += 'src/main/kotlin' +} + +shadowJar() { + archiveName = 'kotlinStrategy.jar' + mergeServiceFiles() + manifest { + attributes 'Main-Class': 'MainKt' + } +} + +build.dependsOn shadowJar + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} diff --git a/paperio/dockers/nodejs9/Dockerfile b/paperio/dockers/nodejs9/Dockerfile new file mode 100644 index 0000000..dbe4046 --- /dev/null +++ b/paperio/dockers/nodejs9/Dockerfile @@ -0,0 +1,9 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Boris Kolganov + +RUN curl -sL https://deb.nodesource.com/setup_9.x | bash - && \ + apt-get install -y nodejs + +ENV SOLUTION_CODE_ENTRYPOINT=main.js +ENV SOLUTION_CODE_PATH=/opt/client/solution +ENV RUN_COMMAND='/usr/bin/node $SOLUTION_CODE_PATH/$SOLUTION_CODE_ENTRYPOINT' diff --git a/paperio/dockers/php7/Dockerfile b/paperio/dockers/php7/Dockerfile new file mode 100644 index 0000000..bac1f5c --- /dev/null +++ b/paperio/dockers/php7/Dockerfile @@ -0,0 +1,10 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Boris Kolganov + +RUN DEBIAN_FRONTEND=noninteractive LC_ALL=en_US.UTF-8 add-apt-repository -y ppa:ondrej/php && \ + apt-get update && \ + apt-get -y install php7.1 php7.1-cli php7.1-common php7.1-json php7.1-mbstring + +ENV SOLUTION_CODE_ENTRYPOINT=main.php +ENV SOLUTION_CODE_PATH=/opt/client/solution +ENV RUN_COMMAND='/usr/bin/php $SOLUTION_CODE_PATH/$SOLUTION_CODE_ENTRYPOINT' diff --git a/paperio/dockers/python2/Dockerfile b/paperio/dockers/python2/Dockerfile new file mode 100644 index 0000000..2d09982 --- /dev/null +++ b/paperio/dockers/python2/Dockerfile @@ -0,0 +1,11 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Boris Kolganov + +RUN apt-get update &&\ + apt-get install -y python python-pip &&\ + pip install numpy scipy keras tensorflow &&\ + apt-get clean + +ENV SOLUTION_CODE_ENTRYPOINT=main.py +ENV SOLUTION_CODE_PATH=/opt/client/solution +ENV RUN_COMMAND='python -u $SOLUTION_CODE_PATH/$SOLUTION_CODE_ENTRYPOINT' diff --git a/paperio/dockers/python3/Dockerfile b/paperio/dockers/python3/Dockerfile new file mode 100644 index 0000000..b4a00f5 --- /dev/null +++ b/paperio/dockers/python3/Dockerfile @@ -0,0 +1,10 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Boris Kolganov + +RUN add-apt-repository ppa:jonathonf/python-3.6 && \ + apt-get update && apt-get install -y python3.6 python3-pip && \ + python3.6 -m pip install -U numpy scipy cython scikit-learn keras pandas tensorflow==1.5.0 + +ENV SOLUTION_CODE_ENTRYPOINT=main.py +ENV SOLUTION_CODE_PATH=/opt/client/solution +ENV RUN_COMMAND='python3.6 -u $SOLUTION_CODE_PATH/$SOLUTION_CODE_ENTRYPOINT' \ No newline at end of file diff --git a/paperio/dockers/rust/Cargo.toml b/paperio/dockers/rust/Cargo.toml new file mode 100644 index 0000000..24dedc4 --- /dev/null +++ b/paperio/dockers/rust/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "solution" +version = "0.1.0" + +[dependencies] +chrono = "0.4.1" +fern = "0.5.5" +flexi_logger = "0.8.1" +lazy_static = "1.0.0" +log = "0.4.1" +num-traits = "0.2.2" +rand = "0.4.2" +serde = "1.0.13" +serde_derive = "1.0.13" +serde_json = "1.0.13" +simplelog = "0.5.1" +time = "0.1.39" diff --git a/paperio/dockers/rust/Dockerfile b/paperio/dockers/rust/Dockerfile new file mode 100644 index 0000000..ceaf337 --- /dev/null +++ b/paperio/dockers/rust/Dockerfile @@ -0,0 +1,14 @@ +FROM stest.tech-mail.ru/aicups/paperio_base + +RUN apt-get update -y && apt-get install -y build-essential curl && curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable +ENV SOLUTION_CODE_PATH=/opt/client/solution \ + SOLUTION_CODE_ENTRYPOINT=src/main.rs \ + COMPILED_FILE_PATH=/opt/client/solution/target/release/solution \ + COMPILATION_COMMAND="RUSTFLAGS=-Awarnings ~/.cargo/bin/cargo build --release --bin solution --manifest-path $SOLUTION_CODE_PATH/Cargo.toml" \ + RUN_COMMAND="/lib64/ld-linux-x86-64.so.2 $MOUNT_POINT" + +COPY Cargo.toml ./ +RUN USER=user ~/.cargo/bin/cargo init --bin $SOLUTION_CODE_PATH &&\ + mv Cargo.toml $SOLUTION_CODE_PATH/Cargo.toml &&\ + ~/.cargo/bin/cargo build --release --manifest-path $SOLUTION_CODE_PATH/Cargo.toml && \ + rm -rf $SOLUTION_CODE_PATH/src/* diff --git a/paperio/dockers/scala/Dockerfile b/paperio/dockers/scala/Dockerfile new file mode 100644 index 0000000..ad5ca7c --- /dev/null +++ b/paperio/dockers/scala/Dockerfile @@ -0,0 +1,24 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Konstantin Aristov + +WORKDIR /opt/client + +RUN apt-get update -y && \ + apt-get install -y apt-utils apt-transport-https software-properties-common && \ + apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823 && \ + add-apt-repository "deb https://dl.bintray.com/sbt/debian /" && \ + apt-get update -y && \ + apt-get install -y scala sbt && \ + rm -rf /var/lib/apt/lists/* && \ + mkdir -p /opt/client/src/main/scala + +COPY build.sbt /opt/client +COPY Main.scala /opt/client/src/main/scala/. +COPY assembly.sbt /opt/client/project/assembly.sbt +RUN sbt assembly && rm -rf target && rm -rf /opt/client/src/main/scala/* + +ENV SOLUTION_CODE_ENTRYPOINT=Main.scala +ENV COMPILED_FILE_PATH=/opt/client/target/scala-2.11/Strategy-assembly-0.1.0.jar +ENV SOLUTION_CODE_PATH=/opt/client/src/main/scala +ENV COMPILATION_COMMAND='sbt -error assembly' +ENV RUN_COMMAND='java -jar $MOUNT_POINT' diff --git a/paperio/dockers/scala/Main.scala b/paperio/dockers/scala/Main.scala new file mode 100644 index 0000000..c7a78ce --- /dev/null +++ b/paperio/dockers/scala/Main.scala @@ -0,0 +1,41 @@ + +import com.rojoma.json.v3.util.JsonUtil +import com.rojoma.json.v3.ast._ + + +object Main { + + def onTick(parsed : Map[String, Array[Map[String, JValue]]]) : Map[String, JValue] = { + + if (parsed.get("Mine").size > 0) { + + val foods = findFood(parsed.get("Objects").get) + + if (foods.size > 0) { + Map("X" -> foods.head.get("X").get.cast[JNumber].get, "Y" -> foods.head.get("Y").get.cast[JNumber].get) + } else { + Map("X" -> JNumber(300), "Y" -> JNumber(300), "Debug" -> JString("No food")) + } + + } else { + Map("X" -> JNumber(0), "Y" -> JNumber(0), "Debug" -> JString("Died")) + } + } + + def findFood(objects : Array[Map[String, JValue]]) : Array[Map[String, JValue]] = { + objects.filter(t => t.get("T").get == JString("F")) + } + + def main(args : Array[String]) { + val configStr = scala.io.StdIn.readLine().trim + val config = JsonUtil.parseJson[Map[String, JValue]](configStr) + + for(tick <- 0 to config.right.get("GAME_TICKS").cast[JNumber].get.toInt) { + val json = scala.io.StdIn.readLine().trim + val data = JsonUtil.parseJson[Map[String, Array[Map[String, JValue]]]](json) + + val reponse = onTick(data.right.get) + println(JsonUtil.renderJson(reponse)) + } + } +} diff --git a/paperio/dockers/scala/assembly.sbt b/paperio/dockers/scala/assembly.sbt new file mode 100644 index 0000000..652a3b9 --- /dev/null +++ b/paperio/dockers/scala/assembly.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6") diff --git a/paperio/dockers/scala/build.sbt b/paperio/dockers/scala/build.sbt new file mode 100644 index 0000000..729500d --- /dev/null +++ b/paperio/dockers/scala/build.sbt @@ -0,0 +1,12 @@ +name := "Strategy" + +version := "0.1.0" + +scalaVersion := "2.11.6" + +libraryDependencies += "com.rojoma" %% "rojoma-json-v3" % "3.8.0" +libraryDependencies += "joda-time" % "joda-time" % "2.9.9" +libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3" +libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.8.0" + +scalacOptions in Test ++= Seq("-Yrangepos") diff --git a/paperio/dockers/swift/Dockerfile b/paperio/dockers/swift/Dockerfile new file mode 100644 index 0000000..04c6d4c --- /dev/null +++ b/paperio/dockers/swift/Dockerfile @@ -0,0 +1,17 @@ +FROM stest.tech-mail.ru/aicups/paperio_base +MAINTAINER Boris Kolganov + +RUN apt-get update && \ + apt-get install -y clang + +RUN apt-get install -y libcurl3 libpython2.7 libpython2.7-dev libcurl4-openssl-dev + +RUN apt-get install -y wget && \ + wget https://swift.org/builds/swift-5.0.2-release/ubuntu1604/swift-5.0.2-RELEASE/swift-5.0.2-RELEASE-ubuntu16.04.tar.gz && \ + tar xzf swift-5.0.2-RELEASE-ubuntu16.04.tar.gz && \ + rm swift-5.0.2-RELEASE-ubuntu16.04.tar.gz && \ + mv swift-5.0.2-RELEASE-ubuntu16.04 /usr/share/swift + +ENV SOLUTION_CODE_ENTRYPOINT=main.swift +ENV SOLUTION_CODE_PATH=/opt/client/solution +ENV RUN_COMMAND='/usr/share/swift/usr/bin/swift $SOLUTION_CODE_PATH/$SOLUTION_CODE_ENTRYPOINT' \ No newline at end of file diff --git a/paperio/examples/cpp_strategy.cpp b/paperio/examples/cpp_strategy.cpp new file mode 100644 index 0000000..7209543 --- /dev/null +++ b/paperio/examples/cpp_strategy.cpp @@ -0,0 +1,22 @@ +#include +#include + +#include "../nlohmann/json.hpp" + +using namespace std; + +int main() { + srand(time(NULL)); + string input_string, input_type; + char commands[4][10] = {"left", "right", "up", "down"}; + + while (true) { + getline(cin, input_string); + auto index = rand() % 4; + nlohmann::json command; + command["command"] = commands[index]; + cout << command.dump() << endl; + } + + return 0; +} \ No newline at end of file diff --git a/paperio/examples/cs_strategy.cs b/paperio/examples/cs_strategy.cs new file mode 100644 index 0000000..68bc324 --- /dev/null +++ b/paperio/examples/cs_strategy.cs @@ -0,0 +1,15 @@ +using System; + +public class Program +{ + public static void Main() + { + var commands = new string[4]{ "left", "right", "up", "down" }; + Random random = new Random(); + while(true) { + var input = Console.ReadLine(); + int index = random.Next(0, commands.Length); + Console.WriteLine("{{\"command\": \"{0}\"}}", commands[index]); + } + } +} \ No newline at end of file diff --git a/paperio/examples/go_strategy.go b/paperio/examples/go_strategy.go new file mode 100644 index 0000000..973f9af --- /dev/null +++ b/paperio/examples/go_strategy.go @@ -0,0 +1,22 @@ +package main + +import ( + "encoding/json" + "fmt" + "math/rand" + "bufio" + "os" +) + + +func main() { + rand.Seed(42) + commands := [4]string{"left", "right", "up", "down"} + reader := bufio.NewReader(os.Stdin) + for { + text, _ := reader.ReadString('\n') + mapD := map[string]string{"command": commands[rand.Intn(4)], "debug": text} + mapB, _ := json.Marshal(mapD) + fmt.Println(string(mapB)) + } +} \ No newline at end of file diff --git a/paperio/examples/java9_strategy.java b/paperio/examples/java9_strategy.java new file mode 100644 index 0000000..a0d3f5d --- /dev/null +++ b/paperio/examples/java9_strategy.java @@ -0,0 +1,22 @@ +import java.util.Scanner; +import java.util.Random; +import java.util.Map; + +public class Main { + public static String getRandom(String[] array) { + int rnd = new Random().nextInt(array.length); + return array[rnd]; + } + + public static void main(String args[]) { + Map emptyImmutableMap = Map.of(); // java 1.9 + + String[] commands = {"left", "right", "up", "down"}; + Scanner scanner = new Scanner(System.in); + while (true) { + String input = scanner.next(); + String command = Main.getRandom(commands); + System.out.printf("{\"command\": \"%s\"}\n", command); + } + } +} diff --git a/paperio/examples/java_strategy.java b/paperio/examples/java_strategy.java new file mode 100644 index 0000000..39aa2ac --- /dev/null +++ b/paperio/examples/java_strategy.java @@ -0,0 +1,19 @@ +import java.util.Scanner; +import java.util.Random; + +public class Main { + public static String getRandom(String[] array) { + int rnd = new Random().nextInt(array.length); + return array[rnd]; + } + + public static void main(String args[]) { + String[] commands = {"left", "right", "up", "down"}; + Scanner scanner = new Scanner(System.in); + while (true) { + String input = scanner.next(); + String command = Main.getRandom(commands); + System.out.printf("{\"command\": \"%s\"}\n", command); + } + } +} diff --git a/paperio/examples/js_strategy.js b/paperio/examples/js_strategy.js new file mode 100644 index 0000000..0de97b0 --- /dev/null +++ b/paperio/examples/js_strategy.js @@ -0,0 +1,15 @@ +const readline = require('readline'); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +let handler = () => { + let commands = ['left', 'right', 'up', 'down']; + let command = commands[Math.floor(Math.random() * commands.length)]; + console.log(JSON.stringify({command, debug: command})); + rl.question('', handler); +}; + +rl.question('', handler); diff --git a/paperio/examples/php_strategy.php b/paperio/examples/php_strategy.php new file mode 100644 index 0000000..7f39521 --- /dev/null +++ b/paperio/examples/php_strategy.php @@ -0,0 +1,12 @@ + $commands[$rand_key], 'debug' => 'debug_msg'); + print json_encode($command)."\n"; +} + +?> \ No newline at end of file diff --git a/paperio/examples/python_strategy.py b/paperio/examples/python_strategy.py new file mode 100755 index 0000000..19ed86d --- /dev/null +++ b/paperio/examples/python_strategy.py @@ -0,0 +1,9 @@ +import json +import random + + +while True: + z = input() + commands = ['left', 'right', 'up', 'down'] + cmd = random.choice(commands) + print(json.dumps({"command": cmd, 'debug': str(z)})) diff --git a/paperio/examples/scala_strategy.scala b/paperio/examples/scala_strategy.scala new file mode 100644 index 0000000..8dfa555 --- /dev/null +++ b/paperio/examples/scala_strategy.scala @@ -0,0 +1,12 @@ +import scala.util.Random + +object Strategy { + def main(args: Array[String]): Unit = { + while(true){ + val input = scala.io.StdIn.readLine() + val commands = Array("left", "right", "up", "down") + val command = Random.shuffle(commands.toList).head + println(String.format("{\"command\": \"%s\"}", command)) + } + } +} \ No newline at end of file diff --git a/paperio/examples/server_python_strategy.py b/paperio/examples/server_python_strategy.py new file mode 100644 index 0000000..03d6975 --- /dev/null +++ b/paperio/examples/server_python_strategy.py @@ -0,0 +1,24 @@ +import asyncio +import json +import random + + +async def tcp_echo_client(loop): + reader, writer = await asyncio.open_connection('127.0.0.1', 8000, loop=loop) + + sid = random.randint(0, 10000) + writer.write('{}\n'.format(json.dumps({"solution_id": sid})).encode()) + + while True: + data = await reader.readline() + print(data.decode()) + + commands = ['left', 'right', 'up', 'down'] + cmd = random.choice(commands) + writer.write('{}\n'.format(json.dumps({"command": cmd, 'debug': cmd})).encode()) + await writer.drain() + + +loop = asyncio.get_event_loop() +loop.run_until_complete(tcp_echo_client(loop)) +loop.close() diff --git a/paperio/examples/swift_strategy.swift b/paperio/examples/swift_strategy.swift new file mode 100644 index 0000000..5428aeb --- /dev/null +++ b/paperio/examples/swift_strategy.swift @@ -0,0 +1,10 @@ +import Foundation + +while true { + let commands: [String] = ["left", "right", "up", "down"] + let command = commands.randomElement()! + let dictionary = ["command": command] + let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, options: []) + let jsonString = String(data: jsonData!, encoding: .utf8) + print(jsonString!) +} \ No newline at end of file diff --git a/paperio/images/localrunner.png b/paperio/images/localrunner.png new file mode 100644 index 0000000000000000000000000000000000000000..ab369f9d60ab5a501f131fccfed20aad8dca61a5 GIT binary patch literal 30353 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

HJH=?aE8_VFN%D-}~UG|S9t4N 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

20XsUM(AR{_Cjrw=^k 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 HUD&??s5qR@ literal 0 HcmV?d00001 diff --git a/paperio/local_runner/Dockerfile b/paperio/local_runner/Dockerfile new file mode 100644 index 0000000..1d89c6c --- /dev/null +++ b/paperio/local_runner/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:18.04 + +WORKDIR /opt/mechanic + +RUN apt-get update && \ + apt-get install -y python3.6 python3-pip && \ + apt-get clean && \ + apt-get autoclean && \ + apt-get autoremove + +COPY requirements.txt ./requirements.txt +RUN pip3 install -r requirements.txt + +COPY . ./ + +EXPOSE 8000 +CMD ["python3", "-u", "./serverrunner.py"] \ No newline at end of file diff --git a/paperio/local_runner/clients.py b/paperio/local_runner/clients.py new file mode 100644 index 0000000..85f5b11 --- /dev/null +++ b/paperio/local_runner/clients.py @@ -0,0 +1,250 @@ +import os +import asyncio +import datetime +import gzip +import json +import random +from subprocess import Popen, PIPE + +import pyglet +from constants import LEFT, RIGHT, UP, DOWN, MAX_EXECUTION_TIME, REQUEST_MAX_TIME + + +class Client(object): + def get_command(self): + pass + + def close(self): + pass + + def send_message(self, t, d): + pass + + def save_log_to_disk(self, log, path): + pass + + def get_solution_id(self): + return random.randint(11000, 12000) + + +class KeyboardClient(Client): + @property + def KEY_COMMAND_MAP(self): + return { + pyglet.window.key.MOTION_LEFT: LEFT, + pyglet.window.key.MOTION_RIGHT: RIGHT, + pyglet.window.key.MOTION_DOWN: DOWN, + pyglet.window.key.MOTION_UP: UP, + } + + def __init__(self, window): + self.last_pressed_button = pyglet.window.key.MOTION_LEFT + + @window.event + def on_key_press(symbol, _): + self.last_pressed_button = symbol + + async def get_command(self): + return {'command': self.KEY_COMMAND_MAP.get(self.last_pressed_button, None)} + + def save_log_to_disk(self, log, path): + pass + + +class KeyboardClient2(KeyboardClient): + @property + def KEY_COMMAND_MAP(self): + return { + pyglet.window.key.A: LEFT, + pyglet.window.key.D: RIGHT, + pyglet.window.key.S: DOWN, + pyglet.window.key.W: UP, + } + + def __init__(self, window): + self.last_pressed_button = pyglet.window.key.A + + @window.event + def on_key_release(symbol, _): + self.last_pressed_button = symbol + + +class SimplePythonClient(Client): + def __init__(self): + self.command = None + self.tick = 0 + self.next_change = None + self.next_dir = 0 + + self.width = None + self.x_cells_count = None + self.y_cells_count = None + self.lines = [] + self.position = None + + def change_command(self): + commands = [LEFT, DOWN, RIGHT, UP] + command = commands[self.next_dir % 4] + self.next_dir += 1 + self.command = command + + def get_next_point(self): + x, y = self.position + + if self.command == UP: + return x, y + self.width + + if self.command == DOWN: + return x, y - self.width + + if self.command == LEFT: + return x - self.width, y + + if self.command == RIGHT: + return x + self.width, y + + def is_border(self, point): + x, y = point + return x < round(self.width / 2) or \ + x > self.x_cells_count * self.width + round(self.width / 2) or \ + y < round(self.width / 2) or \ + y > self.y_cells_count * self.width + round(self.width / 2) + + def is_empty_next_point(self): + if not self.position: + return True + next_point = self.get_next_point() + return next_point not in self.lines and not self.is_border(next_point) + + async def get_command(self): + if not self.next_change or self.next_change == 0 or not self.is_empty_next_point(): + self.next_change = random.randint(1, 4) + self.change_command() + attempts = 0 + while not self.is_empty_next_point() and attempts < 3: + self.change_command() + attempts += 1 + + self.tick += 1 + self.next_change -= 1 + return {'command': self.command} + + def save_log_to_disk(self, log, path): + pass + + def send_message(self, t, d): + if t == 'start_game': + self.width = d['width'] + self.x_cells_count = d['x_cells_count'] + self.y_cells_count = d['y_cells_count'] + + if t == 'tick': + p_data = d['players']['i'] + self.lines = p_data['lines'] + self.position = p_data['position'] + + if t == 'end_game': + pass + + +class TcpClient(Client): + EXECUTION_LIMIT = datetime.timedelta(seconds=MAX_EXECUTION_TIME) + + def __init__(self, reader, writer): + self.reader = reader + self.writer = writer + self.execution_time = datetime.timedelta() + self.solution_id = None + + def save_log_to_disk(self, log, path): + location = path.format(str(self.solution_id) + '.gz') + + with gzip.open(location, 'wb') as f: + f.write(json.dumps(log).encode()) + + return { + 'filename': os.path.basename(location), + 'is_private': True, + 'location': location + } + + async def set_solution_id(self): + hello_json = await asyncio.wait_for(self.reader.readline(), timeout=REQUEST_MAX_TIME) + try: + self.solution_id = json.loads(hello_json.decode('utf-8')).get('solution_id') + except ValueError: + pass + + return bool(self.solution_id) + + def send_message(self, t, d): + msg = { + 'type': t, + 'params': d + } + msg_bytes = '{}\n'.format(json.dumps(msg)).encode() + self.writer.write(msg_bytes) + + async def get_command(self): + try: + before = datetime.datetime.now() + z = await asyncio.wait_for(self.reader.readline(), timeout=REQUEST_MAX_TIME) + if not z: + raise ConnectionError('Connection closed') + self.execution_time += (datetime.datetime.now() - before) + if self.execution_time > self.EXECUTION_LIMIT: + raise Exception('sum timeout error') + except asyncio.TimeoutError: + raise asyncio.TimeoutError('read timeout error') + try: + z = json.loads(z.decode()) + except ValueError: + z = {'debug': 'cant pars json'} + + return z + + def close(self): + self.writer.close() + + def get_solution_id(self): + return self.solution_id + + +class FileClient(Client): + def __init__(self, path_to_script, path_to_log=None): + self.process = Popen(path_to_script, stdout=PIPE, stdin=PIPE) + self.last_message = None + if path_to_log is None: + base_dir = os.getcwd() + now = datetime.datetime.now().strftime('%Y_%m_%d-%H-%M-%S.log.gz') + self.path_to_log = os.path.join(base_dir, now) + else: + self.path_to_log = path_to_log + + def send_message(self, t, d): + msg = { + 'type': t, + 'params': d + } + msg_bytes = '{}\n'.format(json.dumps(msg)).encode() + + self.process.stdin.write(msg_bytes) + self.process.stdin.flush() + + async def get_command(self): + try: + line = self.process.stdout.readline().decode('utf-8') + state = json.loads(line) + return state + except Exception as e: + return {'debug': str(e)} + + def save_log_to_disk(self, log, _): + with gzip.open(self.path_to_log, 'w') as f: + f.write(json.dumps(log).encode()) + + return { + 'filename': os.path.basename(self.path_to_log), + 'is_private': True, + 'location': self.path_to_log + } diff --git a/paperio/local_runner/constants.py b/paperio/local_runner/constants.py new file mode 100644 index 0000000..cae8c51 --- /dev/null +++ b/paperio/local_runner/constants.py @@ -0,0 +1,55 @@ +import os +import json + + +def toint(value, default=None): + try: + return int(value) + except (TypeError, ValueError): + return default + + +def parse_json(value, default=None): + try: + return json.loads(value) + except (TypeError, json.decoder.JSONDecodeError): + return default + + +LEFT = 'left' +RIGHT = 'right' +UP = 'up' +DOWN = 'down' + +SPEED = toint(os.getenv('SPEED'), 5) +WIDTH = toint(os.getenv('WIDTH'), 30) # должно делиться на 2 +BONUS_CHANCE = toint(os.getenv('BONUS_CHANCE'), 500) # 1 из BONUS_CHANCE +BONUSES_MAX_COUNT = toint(os.getenv('BONUSES_MAX_COUNT'), 3) +Y_CELLS_COUNT = toint(os.getenv('Y_CELLS_COUNT'), 31) +X_CELLS_COUNT = toint(os.getenv('X_CELLS_COUNT'), 31) + +NEUTRAL_TERRITORY_SCORE = toint(os.getenv('NEUTRAL_TERRITORY_SCORE'), 1) +ENEMY_TERRITORY_SCORE = toint(os.getenv('ENEMY_TERRITORY_SCORE'), 5) +SAW_SCORE = toint(os.getenv('SAW_SCORE'), 30) +LINE_KILL_SCORE = toint(os.getenv('LINE_KILL_SCORE'), 50) +SAW_KILL_SCORE = toint(os.getenv('SAW_KILL_SCORE'), 150) + +LR_CLIENTS_MAX_COUNT = toint(os.getenv('LR_CLIENTS_MAX_COUNT'), 6) + +MAX_EXECUTION_TIME = toint(os.getenv('MAX_EXECUTION_TIME'), 120) +REQUEST_MAX_TIME = toint(os.getenv('REQUEST_MAX_TIME'), 5) +MAX_TICK_COUNT = toint(os.getenv('MAX_TICK_COUNT'), 1500) +CLIENTS_COUNT = toint(os.getenv('CLIENTS_COUNT'), 2) +AVAILABLE_BONUSES = parse_json(os.getenv('AVAILABLE_BONUSES'), ['n', 's', 'saw']) + +PLAYER_COLORS = [ + (90, 159, 153, 255), + (216, 27, 96, 255), + (96, 125, 139, 255), + (245, 124, 0, 255), + (92, 107, 192, 255), + (141, 110, 99, 255) +] + +WINDOW_HEIGHT = Y_CELLS_COUNT * WIDTH +WINDOW_WIDTH = X_CELLS_COUNT * WIDTH diff --git a/paperio/local_runner/game_objects/__init__.py b/paperio/local_runner/game_objects/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/paperio/local_runner/game_objects/bonuses.py b/paperio/local_runner/game_objects/bonuses.py new file mode 100644 index 0000000..3732ee6 --- /dev/null +++ b/paperio/local_runner/game_objects/bonuses.py @@ -0,0 +1,146 @@ +import random + +from helpers import draw_square_with_image, get_random_coordinates, batch_draw +from constants import WIDTH + + +class Bonus: + image_path = None + color = None + name = None + visio_name = None + + def __init__(self, point): + x, y = point + self.x = x + self.y = y + self.tick = 0 + self.active_ticks = self.generate_active_ticks() + + @staticmethod + def generate_active_ticks(): + return random.choice([i * 10 for i in range(1, 6)]) + + @staticmethod + def is_available_point(x, y, players, busy_points): + for p in players: + if (p.x - 2 * WIDTH <= x <= p.x + 2 * WIDTH) and (p.y - 2 * WIDTH <= y <= p.y + 2 * WIDTH): + return False + return (x, y) not in busy_points + + @staticmethod + def generate_coordinates(players, busy_points): + x, y = get_random_coordinates() + while not Bonus.is_available_point(x, y, players, busy_points): + x, y = get_random_coordinates() + return x, y + + def draw(self): + draw_square_with_image((self.x, self.y), self.color, self.image_path, self.active_ticks) + + def is_ate(self, player, captured): + return (self.x, self.y) == (player.x, player.y) or (self.x, self.y) in captured + + def get_remaining_ticks(self): + return self.active_ticks - self.tick + + def cancel(self, player): + pass + + def get_state(self): + return {'type': self.visio_name, 'position': (self.x, self.y)} + + +class Nitro(Bonus): + color = (255, 249, 221, 255) + image_path = 'sprites/flash.png' + name = 'Нитро' + visio_name = 'n' + + def apply(self, player): + b = [b for b in player.bonuses if type(b) == type(self)] + if len(b) > 0: + b[0].active_ticks += self.active_ticks + else: + player.bonuses.append(self) + + while player.speed < WIDTH: + player.speed += 1 + if WIDTH % player.speed == 0: + break + + def cancel(self, player): + while player.speed > 1: + player.speed -= 1 + if WIDTH % player.speed == 0: + break + + +class Slowdown(Bonus): + color = (234, 249, 255, 255) + image_path = 'sprites/explorer.png' + name = 'Замедление' + visio_name = 's' + + def apply(self, player): + b = [b for b in player.bonuses if type(b) == type(self)] + if len(b) > 0: + b[0].active_ticks += self.active_ticks + else: + player.bonuses.append(self) + + while player.speed > 1: + player.speed -= 1 + if WIDTH % player.speed == 0: + break + + def cancel(self, player): + while player.speed < WIDTH: + player.speed += 1 + if WIDTH % player.speed == 0: + break + + +class Saw(Bonus): + color = (226, 228, 226, 255) + image_path = 'sprites/saw.png' + name = 'Пила' + visio_name = 'saw' + lines = [] + opacity_step = 10 + line_color = (189, 236, 246) + territories = [] + log = [] + + def apply(self, player): + self.active_ticks = 0 + player.bonuses.append(self) + + def cancel(self, player): + pass + + @staticmethod + def append_line(line): + Saw.lines.append([255, line]) + + @staticmethod + def append_territory(territory, color): + Saw.territories.append([color, territory]) + + @staticmethod + def draw_territories(): + for pair in Saw.territories[:]: + pair[0] = [*pair[0][:3], pair[0][3] - Saw.opacity_step] + if pair[0][3] <= 0: + Saw.territories.remove(pair) + else: + batch_draw(pair[1], pair[0]) + + @staticmethod + def draw_lines(): + for pair in Saw.lines[:]: + pair[0] -= Saw.opacity_step + if pair[0] <= 0: + Saw.lines.remove(pair) + else: + batch_draw(pair[1], (*Saw.line_color, pair[0])) diff --git a/paperio/local_runner/game_objects/game.py b/paperio/local_runner/game_objects/game.py new file mode 100644 index 0000000..c54b234 --- /dev/null +++ b/paperio/local_runner/game_objects/game.py @@ -0,0 +1,356 @@ +import os +import json +import gzip +import random + +from helpers import is_intersect +from constants import WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH, PLAYER_COLORS, MAX_TICK_COUNT, BONUS_CHANCE, \ + BONUSES_MAX_COUNT, X_CELLS_COUNT, Y_CELLS_COUNT, SPEED, NEUTRAL_TERRITORY_SCORE, ENEMY_TERRITORY_SCORE, \ + LINE_KILL_SCORE, SAW_KILL_SCORE, AVAILABLE_BONUSES, SAW_SCORE +from game_objects.player import Player +from game_objects.bonuses import Nitro, Slowdown, Bonus, Saw + + +class Game: + border_color = (144, 163, 174, 255) + available_bonuses = [b for b in [Nitro, Slowdown, Saw] if b.visio_name in AVAILABLE_BONUSES] + + RESULT_LOCATION = os.environ.get('GAME_LOG_LOCATION', './result') + + BASE_DIR = os.path.dirname(RESULT_LOCATION) + + VISIO_LOCATION = os.path.join(BASE_DIR, 'visio.gz') + SCORES_LOCATION = os.path.join(BASE_DIR, 'scores.json') + DEBUG_LOCATION = os.path.join(BASE_DIR, '{}') + + def get_busy_points(self): + players_points = {(p.x, p.y) for p in self.players} + bonuses_points = {(b.x, b.y) for b in self.bonuses} + lines_poins = set() + for player in self.players: + lines_poins |= {i for i in player.lines} + + return players_points | bonuses_points | lines_poins + + def generate_bonus(self): + if len(self.available_bonuses) > 0: + if random.randint(1, BONUS_CHANCE) == 1 and len(self.bonuses) < BONUSES_MAX_COUNT: + coors = Bonus.generate_coordinates(self.players, self.get_busy_points()) + bonus = random.choice(self.available_bonuses)(coors) + self.bonuses.append(bonus) + + def get_coordinates(self, clients_count): + dx = round(X_CELLS_COUNT / 6) * WIDTH + dy = round(Y_CELLS_COUNT / 6) * WIDTH + + if clients_count == 1: + coors = [(3 * dx, 3 * dy)] + elif clients_count == 2: + coors = [ + (2 * dx, 3 * dy), + (4 * dx, 3 * dy), + ] + elif clients_count <= 4: + coors = [ + (2 * dx, 2 * dy), + (2 * dx, 4 * dy), + (4 * dx, 2 * dy), + (4 * dx, 4 * dy), + ] + else: + x = round(X_CELLS_COUNT / 5) * WIDTH + y = (WINDOW_HEIGHT + WINDOW_WIDTH - 4 * x) / 3 + b = (WINDOW_WIDTH - 2 * x) / 2 + a = y - b + + coors = [ + (x, x + a), + (x, x + a + y + WIDTH), + + (round(WINDOW_WIDTH / 2), WINDOW_HEIGHT - x + WIDTH), + (round(WINDOW_WIDTH / 2), x), + + (WINDOW_WIDTH - x + WIDTH, x + a), + (WINDOW_WIDTH - x + WIDTH, x + a + y + WIDTH), + ] + + coors = [(round(x / WIDTH) * WIDTH - round(WIDTH / 2), round(y / WIDTH) * WIDTH - round(WIDTH / 2)) for x, y in coors] + yield from coors + + def __init__(self, clients): + players = [] + coordinates = self.get_coordinates(len(clients)) + for index, client in enumerate(clients): + players.append(Player(index + 1, *next(coordinates), 'Player {}'.format(index + 1), PLAYER_COLORS[index], client)) + + self.players = players + self.losers = [] + self.bonuses = [] + self.game_log = [] + self.tick = 1 + + def check_loss(self, player, players): + is_loss = False + + if player.y < 0 + round(WIDTH / 2): + is_loss = True + + if player.y > WINDOW_HEIGHT - round(WIDTH / 2): + is_loss = True + + if player.x < 0 + round(WIDTH / 2): + is_loss = True + + if player.x > WINDOW_WIDTH - round(WIDTH / 2): + is_loss = True + + for p in players: + if (p.x, p.y) in player.lines: + if p != player: + p.score += LINE_KILL_SCORE + is_loss = True + + for p in players: + if is_intersect((player.x, player.y), (p.x, p.y)) and p != player: + if len(player.lines) >= len(p.lines): + is_loss = True + + if len(player.territory.points) == 0: + is_loss = True + + return is_loss + + def send_game_start(self): + start_message = { + 'x_cells_count': X_CELLS_COUNT, + 'y_cells_count': Y_CELLS_COUNT, + 'speed': SPEED, + 'width': WIDTH + } + self.game_log.append({'type': 'start_game', **start_message}) + for player in self.players: + player.send_message('start_game', start_message) + + def send_game_end(self): + self.game_log.append({ + 'type': 'end_game' + }) + for player in self.players: + player.send_message('end_game', {}) + + def send_game_tick(self): + self.game_log.append({ + 'type': 'tick', + 'players': self.get_players_states(), + 'bonuses': self.get_bonuses_states(), + 'tick_num': self.tick, + 'saw': Saw.log + }) + + for player in self.players: + if (player.x - round(WIDTH / 2)) % WIDTH == 0 and (player.y - round(WIDTH / 2)) % WIDTH == 0: + player.send_message('tick', { + 'players': self.get_players_states(player), + 'bonuses': self.get_bonuses_states(), + 'tick_num': self.tick, + }) + + Saw.log = [] + + async def game_loop_wrapper(self, *args, **kwargs): + self.send_game_start() + while True: + is_game_over = await self.game_loop(*args, **kwargs) + print('tick: {}'.format(self.tick)) + if is_game_over or self.tick >= MAX_TICK_COUNT: + self.send_game_end() + self.game_save() + break + + def get_players_states(self, player=None): + states = {p.id: p.get_state() for p in self.players} + + if player: + states['i'] = states.pop(player.id) + + return states + + def get_bonuses_states(self): + return [b.get_state() for b in self.bonuses] + + async def game_loop(self, *args, **kwargs): + self.send_game_tick() + + for player in self.players: + if (player.x - round(WIDTH / 2)) % WIDTH == 0 and (player.y - round(WIDTH / 2)) % WIDTH == 0: + command = await player.get_command(self.tick) + if command: + player.change_direction(command) + + for player in self.players: + player.move() + + for index, player in enumerate(self.players): + is_loss = self.check_loss(player, self.players) + if is_loss: + self.losers.append(self.players[index]) + + for player in self.players: + player.remove_saw_bonus() + + if (player.x - round(WIDTH / 2)) % WIDTH == 0 and (player.y - round(WIDTH / 2)) % WIDTH == 0: + player.update_lines() + + captured = player.territory.capture(player.lines) + if len(captured) > 0: + player.lines.clear() + player.score += NEUTRAL_TERRITORY_SCORE * len(captured) + + player.tick_action() + + for bonus in self.bonuses[:]: + if bonus.is_ate(player, captured): + bonus.apply(player) + self.bonuses.remove(bonus) + + if isinstance(bonus, Saw): + line = player.get_direction_line() + Saw.append_line(line) + for p in self.players: + if p != player: + if any([is_intersect((p.x, p.y), point) for point in line]): + self.losers.append(p) + Saw.log.append({ + 'player': player.id, + 'loser': p.id, + 'killed': True + }) + player.score += SAW_KILL_SCORE + else: + removed = p.territory.split(line, player.direction, p) + if len(removed) > 0: + player.score += SAW_SCORE + Saw.append_territory(removed, p.territory.color) + Saw.log.append({ + 'player': player.id, + 'loser': p.id, + 'points': removed, + 'killed': False + }) + for p in self.players: + if p != player: + removed = p.territory.remove_points(captured) + player.score += (ENEMY_TERRITORY_SCORE - NEUTRAL_TERRITORY_SCORE) * len(removed) + + for player in self.losers: + if player in self.players: + self.players.remove(player) + + self.generate_bonus() + + self.tick += 1 + return len(self.players) == 0 + + def save_scores(self): + d = {p.client.get_solution_id(): p.score for p in self.losers} + + with open(self.SCORES_LOCATION, 'w') as f: + f.write(json.dumps(d)) + + return { + "filename": os.path.basename(self.SCORES_LOCATION), + "location": self.SCORES_LOCATION, + "is_private": False + } + + def get_players_external_id(self): + return {p.id: p.client.get_solution_id() for p in self.losers + self.players} + + def save_visio(self): + d = { + 'config': self.get_players_external_id(), + 'visio_info': self.game_log + } + with gzip.open(self.VISIO_LOCATION, 'wb') as f: + f.write(json.dumps(d).encode()) + return { + "filename": os.path.basename(self.VISIO_LOCATION), + "location": self.VISIO_LOCATION, + "is_private": False + } + + def save_debug(self): + return [ + p.save_log(self.DEBUG_LOCATION) for p in self.losers + ] + + def game_save(self): + result = { + "scores": self.save_scores(), + "debug": self.save_debug(), + "visio": self.save_visio() + } + + with open(self.RESULT_LOCATION, 'w') as f: + f.write(json.dumps(result)) + + +class LocalGame(Game): + border_color = (144, 163, 174, 255) + + def __init__(self, clients, scene, timeout): + super().__init__(clients) + self.scene = scene + self.timeout = timeout + + def show_bonuses(self): + for player in self.players: + if len(player.bonuses) > 0: + for bonus in player.bonuses: + label = '{} - {} - {}'.format(player.name, bonus.name, bonus.get_remaining_ticks()) + self.scene.append_label_to_leaderboard(label, player.color) + + def show_losers(self): + for player in self.losers: + label = '{} выбыл, результат: {}'.format(player.name, player.score) + self.scene.append_label_to_leaderboard(label, player.color) + + def show_score(self): + for player in self.players: + label = '{} результат: {}'.format(player.name, player.score) + self.scene.append_label_to_leaderboard(label, player.color) + + def draw_bonuses(self): + for bonus in self.bonuses: + bonus.draw() + + def draw(self): + for player in self.players: + player.territory.draw() + + Saw.draw_lines() + Saw.draw_territories() + + for player in self.players: + player.draw_lines() + + for player in self.players: + player.draw_position() + + if len(self.players) == 0: + self.scene.show_game_over() + elif self.timeout and self.tick >= MAX_TICK_COUNT: + self.scene.show_game_over(timeout=True) + + self.draw_bonuses() + + self.scene.draw_leaderboard() + self.show_losers() + self.show_score() + self.show_bonuses() + self.scene.reset_leaderboard() + + async def game_loop(self, *args, **kwargs): + self.scene.clear() + self.draw() + return await super().game_loop(*args, **kwargs) diff --git a/paperio/local_runner/game_objects/player.py b/paperio/local_runner/game_objects/player.py new file mode 100644 index 0000000..8228d62 --- /dev/null +++ b/paperio/local_runner/game_objects/player.py @@ -0,0 +1,157 @@ +from copy import copy +from game_objects.territory import Territory +from game_objects.bonuses import Saw +from constants import UP, DOWN, LEFT, RIGHT, SPEED, WINDOW_HEIGHT, WINDOW_WIDTH, WIDTH +from helpers import batch_draw, draw_square + + +class Player: + speed = SPEED + direction = LEFT + + def __init__(self, id, x, y, name, color, client): + self.id = id + self.x = x + self.y = y + self.color = [i - 25 if i >= 25 else i for i in color[:-1]] + [color[-1]] + self.line_color = list(color[:-1]) + [160] + self.territory = Territory(x, y, color) + self.lines = [] + self.bonuses = [] + self.name = name + self.score = 0 + + self.debug_log = [] + self.client = client + self.is_disconnected = False + + def change_direction(self, command): + if command == UP and self.direction != DOWN: + self.direction = UP + + if command == DOWN and self.direction != UP: + self.direction = DOWN + + if command == LEFT and self.direction != RIGHT: + self.direction = LEFT + + if command == RIGHT and self.direction != LEFT: + self.direction = RIGHT + + def move(self): + if self.direction == UP: + self.y += self.speed + + if self.direction == DOWN: + self.y -= self.speed + + if self.direction == LEFT: + self.x -= self.speed + + if self.direction == RIGHT: + self.x += self.speed + + def draw_lines(self): + batch_draw(self.lines, self.line_color) + + def draw_position(self): + draw_square((self.x, self.y), self.color) + + def update_lines(self): + if (self.x, self.y) not in self.territory.points or len(self.lines) > 0: + self.lines.append((self.x, self.y)) + + def send_message(self, t, d): + if self.is_disconnected: + return + try: + self.client.send_message(t, d) + except Exception as e: + print('write exception', self.client.get_solution_id(), e) + self.is_disconnected = True + self.client.close() + + def remove_saw_bonus(self): + for bonus in self.bonuses[:]: + if isinstance(bonus, Saw): + bonus.cancel(self) + self.bonuses.remove(bonus) + + def tick_action(self): + for bonus in self.bonuses[:]: + bonus.tick += 1 + + if bonus.tick >= bonus.active_ticks: + bonus.cancel(self) + self.bonuses.remove(bonus) + + def get_bonuses_state(self): + return [{'type': b.visio_name, 'ticks': b.get_remaining_ticks()} for b in self.bonuses] + + def get_state(self): + return { + 'score': self.score, + 'direction': self.direction, + 'territory': list(self.territory.points), + 'lines': copy(self.lines), + 'position': (self.x, self.y), + 'bonuses': self.get_bonuses_state() + } + + async def get_command(self, tick): + if self.is_disconnected: + return + + try: + client_answer = await self.client.get_command() + if client_answer: + debug_info = client_answer.get('debug') + error_info = client_answer.get('error') + if debug_info: + self.debug_log.append({ + 'tick': tick, + 'message': debug_info[:200] + }) + + if error_info: + self.debug_log.append({ + 'tick': tick, + 'message': error_info[:200] + }) + + return client_answer.get('command') + except Exception as e: + args = e.args + if len(args) > 0: + self.debug_log.append({'tick': tick, 'message': args[0]}) + else: + self.debug_log.append({'tick': tick, 'message': str(e)}) + print('read exception', self.client.get_solution_id(), e) + self.is_disconnected = True + self.client.close() + + def save_log(self, path): + return self.client.save_log_to_disk(self.debug_log, path) + + def _get_line(self, dx, dy): + x, y = self.x, self.y + points = [] + while 0 < x < WINDOW_WIDTH and 0 < y < WINDOW_HEIGHT: + x += dx + y += dy + points.append((x, y)) + + return points + + def get_direction_line(self): + if self.direction == UP: + return self._get_line(0, WIDTH) + + if self.direction == DOWN: + return self._get_line(0, -WIDTH) + + if self.direction == LEFT: + return self._get_line(-WIDTH, 0) + + if self.direction == RIGHT: + return self._get_line(WIDTH, 0) diff --git a/paperio/local_runner/game_objects/scene.py b/paperio/local_runner/game_objects/scene.py new file mode 100644 index 0000000..7858586 --- /dev/null +++ b/paperio/local_runner/game_objects/scene.py @@ -0,0 +1,59 @@ +import pyglet + +from constants import WINDOW_HEIGHT, WINDOW_WIDTH, WIDTH +from helpers import draw_quadrilateral, draw_line + + +class Scene: + background_color = (220 / 255, 240 / 255, 244 / 255, 1) + border_color = (144, 163, 174, 255) + game_over_label_color = (95, 99, 104, 255) + + leaderboard_color = (255, 255, 255, 128) + leaderboard_width = 320 + leaderboard_height = 240 + + leaderboard_rows_count = 0 + + def __init__(self): + self.window = pyglet.window.Window(height=WINDOW_HEIGHT, width=WINDOW_WIDTH) + pyglet.gl.glClearColor(*self.background_color) + pyglet.gl.glEnable(pyglet.gl.GL_BLEND) + pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA, pyglet.gl.GL_ONE_MINUS_SRC_ALPHA) + + def clear(self): + self.window.clear() + + def append_label_to_leaderboard(self, label, color): + pyglet.text.Label(label, + font_name='Times New Roman', + font_size=16, + color=color, + x=WINDOW_WIDTH - self.leaderboard_width + 20, + y=WINDOW_HEIGHT - 20 - WIDTH / 2 - 30 * self.leaderboard_rows_count, + anchor_x='left', anchor_y='center').draw() + self.leaderboard_rows_count += 1 + + def reset_leaderboard(self): + self.leaderboard_rows_count = 0 + + def show_game_over(self, timeout=False): + label = 'TIMEOUT' if timeout else 'GAME OVER' + pyglet.text.Label(label, font_name='Times New Roman', + font_size=30, + color=self.game_over_label_color, + x=WINDOW_WIDTH / 2, y=WINDOW_HEIGHT / 2, + anchor_x='center', anchor_y='center').draw() + + def draw_border(self): + draw_line((0, 0), (0, WINDOW_HEIGHT), self.border_color) + draw_line((0, WINDOW_HEIGHT), (WINDOW_WIDTH, WINDOW_HEIGHT), self.border_color) + draw_line((WINDOW_WIDTH, WINDOW_HEIGHT), (WINDOW_WIDTH, 0), self.border_color) + draw_line((WINDOW_WIDTH, 0), (0, 0), self.border_color) + + def draw_leaderboard(self): + draw_quadrilateral((WINDOW_WIDTH - self.leaderboard_width, WINDOW_HEIGHT - self.leaderboard_height, + WINDOW_WIDTH, WINDOW_HEIGHT - self.leaderboard_height, + WINDOW_WIDTH, WINDOW_HEIGHT, + WINDOW_WIDTH - self.leaderboard_width, WINDOW_HEIGHT), + self.leaderboard_color) diff --git a/paperio/local_runner/game_objects/territory.py b/paperio/local_runner/game_objects/territory.py new file mode 100644 index 0000000..58718e2 --- /dev/null +++ b/paperio/local_runner/game_objects/territory.py @@ -0,0 +1,155 @@ +from helpers import in_polygon, batch_draw_territory, get_neighboring, get_vert_and_horiz +from constants import WIDTH, LEFT, RIGHT, UP, DOWN +import networkx as nx + + +class Territory: + def __init__(self, x, y, color): + self.color = color + self.points = {(x, y), *get_neighboring((x, y))} + self.changed = True + + def draw(self): + batch_draw_territory(self.points, self.color, self.changed) + self.changed = False + + def get_boundary(self): + boundary = [] + for point in self.points: + if any([neighboring not in self.points for neighboring in get_neighboring(point)]): + boundary.append(point) + return boundary + + def get_nearest_boundary(self, point, boundary): + for neighbor in [point, *get_neighboring(point)]: + if neighbor in boundary: + return neighbor + + def _capture(self, boundary): + poligon_x_arr = [x for x, _ in boundary] + poligon_y_arr = [y for _, y in boundary] + + max_x = max(poligon_x_arr) + max_y = max(poligon_y_arr) + min_x = min(poligon_x_arr) + min_y = min(poligon_y_arr) + + captured = [] + x = max_x + while x > min_x: + y = max_y + while y > min_y: + if (x, y) not in self.points and in_polygon(x, y, poligon_x_arr, poligon_y_arr): + self.points.add((x, y)) + captured.append((x, y)) + y -= WIDTH + x -= WIDTH + return captured + + def get_voids_between_lines_and_territory(self, lines): + boundary = self.get_boundary() + voids = [] + for cur in lines: + for point in get_neighboring(cur): + if point in boundary: + start_point = self.get_nearest_boundary(lines[0], boundary) + if start_point: + end_index = boundary.index(point) + start_index = boundary.index(start_point) + + try: + path = self.get_path(start_index, end_index, boundary) + except (nx.NetworkXNoPath, nx.NodeNotFound): + continue + + if len(path) > 1 and path[0] == path[-1]: + path = path[1:] + + path = [boundary[index] for index in path] + lines_path = lines[:lines.index(cur) + 1] + + voids.append(lines_path + path) + return voids + + def capture_voids_between_lines(self, lines): + captured = [] + for index, cur in enumerate(lines): + for point in get_neighboring(cur): + if point in lines: + end_index = lines.index(point) + path = lines[index:end_index] + if len(path) >= 8: + captured.extend(self._capture(path)) + return captured + + def capture(self, lines): + captured = [] + if len(lines) > 1: + if lines[-1] in self.points: + voids = self.get_voids_between_lines_and_territory(lines) + + captured.extend(self.capture_voids_between_lines(lines)) + + for line in lines: + if line not in self.points: + self.points.add(line) + captured.append(line) + + for void in voids: + captured.extend(self._capture(void)) + if len(captured) > 0: + self.changed = True + return captured + + def remove_points(self, points): + removed = [] + for point in points: + if point in self.points: + self.points.discard(point) + removed.append(point) + + if len(removed) > 0: + self.changed = True + return removed + + def get_siblings(self, point, boundary): + return [sibling for sibling in get_vert_and_horiz(point) if sibling in boundary] + + def get_path(self, start_index, end_index, boundary): + graph = nx.Graph() + for index, point in enumerate(boundary): + siblings = self.get_siblings(point, boundary) + for sibling in siblings: + graph.add_edge(index, boundary.index(sibling), weight=1) + + return nx.shortest_path(graph, end_index, start_index, weight='weight') + + def split(self, line, direction, player): + removed = [] + l_point = line[0] + + if any([point in self.points for point in line]): + for point in list(self.points): + if direction in [UP, DOWN]: + if player.x < l_point[0]: + if point[0] >= l_point[0]: + removed.append(point) + self.points.discard(point) + else: + if point[0] <= l_point[0]: + removed.append(point) + self.points.discard(point) + + if direction in [LEFT, RIGHT]: + if player.y < l_point[1]: + if point[1] >= l_point[1]: + removed.append(point) + self.points.discard(point) + else: + if point[1] <= l_point[1]: + removed.append(point) + self.points.discard(point) + + if len(removed) > 0: + self.changed = True + return removed diff --git a/paperio/local_runner/helpers.py b/paperio/local_runner/helpers.py new file mode 100644 index 0000000..d2fbe7d --- /dev/null +++ b/paperio/local_runner/helpers.py @@ -0,0 +1,204 @@ +import random +import pyglet +from constants import WIDTH, X_CELLS_COUNT, Y_CELLS_COUNT + + +def show_coordinates(point): + x, y = point + pyglet.text.Label('{}, {}'.format(x, y), font_name='Times New Roman', + font_size=9, + color=(95, 99, 104, 255), + x=x, y=y, + anchor_x='center', anchor_y='center').draw() + + +def get_square_coordinates(point, width=WIDTH): + x, y = point + return (x - width, y - width, + x + width, y - width, + x + width, y + width, + x - width, y + width) + + +def get_diagonals(point, width=WIDTH): + x, y = point + + return [ + (x + width, y + width), + (x - width, y + width), + (x + width, y - width), + (x - width, y - width) + ] + + +def get_vert_and_horiz(point, width=WIDTH): + x, y = point + + return [ + (x, y + width), + (x - width, y), + (x, y - width), + (x + width, y), + ] + + +def get_neighboring(point, width=WIDTH): + return [ + *get_vert_and_horiz(point, width), + *get_diagonals(point, width) + ] + + +def get_territory_line(point, points): + line_points = [] + + p = point + while p in points: + line_points.append(p) + x, y = p + p = (x - WIDTH, y) + start = (p[0] + WIDTH, p[1]) + + p = point + while p in points: + line_points.append(p) + x, y = p + p = (x + WIDTH, y) + end = (p[0] - WIDTH, p[1]) + + return line_points, start, end + + +def get_line_coordinates(start, end, width=WIDTH): + width = round(width / 2) + x1, y1 = start + x2, y2 = end + return [ + (x2 + width, y2 + width), + (x2 + width, y2 - width), + (x1 - width, y1 - width), + (x1 - width, y1 + width), + ] + + +TERRITORY_CACHE = {} + + +def batch_draw_territory(points, color, redraw, width=WIDTH): + if len(points) < 100: + batch_draw(points, color, width) + return + + if color not in TERRITORY_CACHE or redraw: + lines = [] + excluded = set() + for point in points: + if point not in excluded: + line_points, start, end = get_territory_line(point, points) + excluded.update(line_points) + coors = get_line_coordinates(start, end, width) + lines.append(coors) + TERRITORY_CACHE[color] = [len(points), lines] + else: + lines = TERRITORY_CACHE[color][1] + + pyglet.gl.glColor4f(*[i/255 for i in color]) + pyglet.gl.glBegin(pyglet.gl.GL_QUADS) + for line in lines: + for coor in line: + pyglet.graphics.glVertex2i(*coor) + pyglet.gl.glEnd() + + +def batch_draw(points, color, width=WIDTH): + width = round(width / 2) + pyglet.gl.glColor4f(*[i/255 for i in color]) + pyglet.gl.glBegin(pyglet.gl.GL_QUADS) + for point in points: + square = get_square_coordinates(point, width) + pyglet.graphics.glVertex2i(square[0], square[1]) + pyglet.graphics.glVertex2i(square[2], square[3]) + pyglet.graphics.glVertex2i(square[4], square[5]) + pyglet.graphics.glVertex2i(square[6], square[7]) + pyglet.gl.glEnd() + + +def draw_square(point, color, width=WIDTH): + width = round(width / 2) + coordinates = get_square_coordinates(point, width) + pyglet.graphics.draw(4, pyglet.gl.GL_QUADS, ('v2i', coordinates), ('c4B', 4 * color)) + + +def draw_quadrilateral(coordinates, color): + pyglet.graphics.draw(4, pyglet.gl.GL_QUADS, ('v2i', coordinates), ('c4B', 4 * color)) + + +def draw_line(point1, point2, color, width=WIDTH): + x1, y1 = point1 + x2, y2 = point2 + + width = round(width / 2) + + coordinates = (x1 - width, y1, + x1 + width, y1, + x2 + width, y2, + x2 - width, y2) + + if y1 == y2: + coordinates = (x1, y1 + width, + x1, y1 - width, + x2, y2 - width, + x2, y2 + width) + + pyglet.graphics.draw(4, pyglet.gl.GL_QUADS, ('v2i', coordinates), ('c4B', 4 * color)) + + +def in_polygon(x, y, xp, yp): + c = 0 + for i in range(len(xp)): + if (((yp[i] <= y and y < yp[i - 1]) or (yp[i - 1] <= y and y < yp[i])) and \ + (x > (xp[i - 1] - xp[i]) * (y - yp[i]) / (yp[i - 1] - yp[i]) + xp[i])): c = 1 - c + return c + + +IMAGE_CACHE = {} + + +def load_image(path): + if path not in IMAGE_CACHE: + img = pyglet.image.load(path) + img.anchor_x = round(img.width / 2) + img.anchor_y = round(img.height / 2) + IMAGE_CACHE[path] = img + + return IMAGE_CACHE[path] + + +def draw_square_with_image(point, color, image_path, label=None, width=WIDTH): + draw_square(point, color, width) + + x, y = point + + img = load_image(image_path) + sprite = pyglet.sprite.Sprite(img=img, x=x, y=y) + sprite.scale = 0.75 * (width / max(sprite.height, sprite.width)) + sprite.draw() + + if label is not None: + pyglet.text.Label('{}'.format(label), font_name='Times New Roman', + font_size=round(WIDTH / 7), + color=(95, 99, 104, 255), + x=x + round(WIDTH / 2), y=y + round(WIDTH / 2), + anchor_x='right', anchor_y='top').draw() + + +def get_random_coordinates(): + x = random.randint(1, X_CELLS_COUNT) * WIDTH - round(WIDTH / 2) + y = random.randint(1, Y_CELLS_COUNT) * WIDTH - round(WIDTH / 2) + return x, y + + +def is_intersect(p1, p2, width=WIDTH): + x1, y1 = p1 + x2, y2 = p2 + return abs(x1 - x2) < width and abs(y1 - y2) < width diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py new file mode 100644 index 0000000..e285ea4 --- /dev/null +++ b/paperio/local_runner/localrunner.py @@ -0,0 +1,76 @@ +from asyncio import events +import argparse + +import pyglet +from pyglet.window import key + +from helpers import TERRITORY_CACHE +from clients import KeyboardClient, SimplePythonClient, FileClient +from constants import LR_CLIENTS_MAX_COUNT, MAX_TICK_COUNT +from game_objects.scene import Scene +from game_objects.game import LocalGame + + +scene = Scene() +loop = events.new_event_loop() +events.set_event_loop(loop) + +parser = argparse.ArgumentParser(description='LocalRunner for paperio') + +for i in range(1, LR_CLIENTS_MAX_COUNT + 1): + parser.add_argument('-p{}'.format(i), '--player{}'.format(i), type=str, nargs='?', + help='Path to executable with strategy for player {}'.format(i)) + parser.add_argument('--p{}l'.format(i), type=str, nargs='?', help='Path to log for player {}'.format(i)) + +parser.add_argument('-t', '--timeout', type=str, nargs='?', help='off/on timeout', default='on') + +args = parser.parse_args() + +clients = [] +for i in range(1, LR_CLIENTS_MAX_COUNT + 1): + arg = getattr(args, 'player{}'.format(i)) + if arg: + if arg == 'keyboard': + client = KeyboardClient(scene.window) + elif arg == 'simple_bot': + client = SimplePythonClient() + else: + client = FileClient(arg.split(), getattr(args, 'p{}l'.format(i))) + + clients.append(client) + +if len(clients) == 0: + clients.append(KeyboardClient(scene.window)) + + +class Runner: + @staticmethod + def game_loop_wrapper(dt): + is_game_over = loop.run_until_complete(Runner.game.game_loop()) + if is_game_over or (args.timeout == 'on' and Runner.game.tick >= MAX_TICK_COUNT): + loop.run_until_complete(Runner.game.game_loop()) + Runner.game.send_game_end() + Runner.game.game_save() + Runner.stop_game() + + @staticmethod + @scene.window.event + def on_key_release(symbol, modifiers): + if symbol == key.R: + Runner.stop_game() + TERRITORY_CACHE.clear() + Runner.run_game() + + @staticmethod + def stop_game(): + pyglet.clock.unschedule(Runner.game_loop_wrapper) + + @staticmethod + def run_game(): + Runner.game = LocalGame(clients, scene, args.timeout == 'on') + Runner.game.send_game_start() + pyglet.clock.schedule_interval(Runner.game_loop_wrapper, 1 / 200) + + +Runner.run_game() +pyglet.app.run() diff --git a/paperio/local_runner/requirements.txt b/paperio/local_runner/requirements.txt new file mode 100644 index 0000000..da9da28 --- /dev/null +++ b/paperio/local_runner/requirements.txt @@ -0,0 +1,2 @@ +pyglet==1.3.2 +networkx==2.3 \ No newline at end of file diff --git a/paperio/local_runner/serverrunner.py b/paperio/local_runner/serverrunner.py new file mode 100644 index 0000000..38221f6 --- /dev/null +++ b/paperio/local_runner/serverrunner.py @@ -0,0 +1,50 @@ +import asyncio + +from clients import TcpClient +from game_objects.game import Game +from constants import CLIENTS_COUNT + + +class GameServer: + def __init__(self): + self.clients = [] + + async def connection_handler(self, client_reader, client_writer): + client = TcpClient(client_reader, client_writer) + is_success = await client.set_solution_id() + + clients_count = len(self.clients) + + if clients_count < CLIENTS_COUNT: + if is_success: + clients_count += 1 + print('{} clients connected'.format(clients_count)) + self.clients.append(client) + else: + loop.stop() + else: + client_writer.close() + + game_future = None + if clients_count == CLIENTS_COUNT: + game = Game(self.clients) + game_future = asyncio.ensure_future(game.game_loop_wrapper()) + + if game_future: + done, pending = await asyncio.wait([game_future]) + if not pending: + loop.stop() + + print('game done') + for place, player in enumerate(reversed(game.losers), 1): + print('Place {}: {}'.format(place, player.name)) + + +gs = GameServer() + +loop = asyncio.get_event_loop() +loop.run_until_complete(asyncio.start_server(gs.connection_handler, '0.0.0.0', 8000)) +try: + loop.run_forever() +finally: + loop.close() diff --git a/paperio/local_runner/sprites/explorer.png b/paperio/local_runner/sprites/explorer.png new file mode 100644 index 0000000000000000000000000000000000000000..0c96c1c10b31eee28e365809f3cc66dfaebf2a97 GIT binary patch literal 33088 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 Date: Thu, 18 Jul 2019 18:53:54 +0300 Subject: [PATCH 41/96] global link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b910722..61c02de 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ # miniaicups Правила, исходники и прочее для aicups.ru -## [Mad Cars](madcars/) +## [Paper IO](paperio/) From 1cabe20256ea7c0fc44078e88ad510357acc90d9 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Thu, 18 Jul 2019 18:55:55 +0300 Subject: [PATCH 42/96] deleted test files --- paperio/examples/server_python_strategy.py | 24 ---------------------- 1 file changed, 24 deletions(-) delete mode 100644 paperio/examples/server_python_strategy.py diff --git a/paperio/examples/server_python_strategy.py b/paperio/examples/server_python_strategy.py deleted file mode 100644 index 03d6975..0000000 --- a/paperio/examples/server_python_strategy.py +++ /dev/null @@ -1,24 +0,0 @@ -import asyncio -import json -import random - - -async def tcp_echo_client(loop): - reader, writer = await asyncio.open_connection('127.0.0.1', 8000, loop=loop) - - sid = random.randint(0, 10000) - writer.write('{}\n'.format(json.dumps({"solution_id": sid})).encode()) - - while True: - data = await reader.readline() - print(data.decode()) - - commands = ['left', 'right', 'up', 'down'] - cmd = random.choice(commands) - writer.write('{}\n'.format(json.dumps({"command": cmd, 'debug': cmd})).encode()) - await writer.drain() - - -loop = asyncio.get_event_loop() -loop.run_until_complete(tcp_echo_client(loop)) -loop.close() From 67d037c4d030523ea542830de50f02952c49b0b2 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Thu, 18 Jul 2019 18:57:17 +0300 Subject: [PATCH 43/96] deleted unnecessary --- paperio/local_runner/Dockerfile | 17 ---------- paperio/local_runner/serverrunner.py | 50 ---------------------------- 2 files changed, 67 deletions(-) delete mode 100644 paperio/local_runner/Dockerfile delete mode 100644 paperio/local_runner/serverrunner.py diff --git a/paperio/local_runner/Dockerfile b/paperio/local_runner/Dockerfile deleted file mode 100644 index 1d89c6c..0000000 --- a/paperio/local_runner/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM ubuntu:18.04 - -WORKDIR /opt/mechanic - -RUN apt-get update && \ - apt-get install -y python3.6 python3-pip && \ - apt-get clean && \ - apt-get autoclean && \ - apt-get autoremove - -COPY requirements.txt ./requirements.txt -RUN pip3 install -r requirements.txt - -COPY . ./ - -EXPOSE 8000 -CMD ["python3", "-u", "./serverrunner.py"] \ No newline at end of file diff --git a/paperio/local_runner/serverrunner.py b/paperio/local_runner/serverrunner.py deleted file mode 100644 index 38221f6..0000000 --- a/paperio/local_runner/serverrunner.py +++ /dev/null @@ -1,50 +0,0 @@ -import asyncio - -from clients import TcpClient -from game_objects.game import Game -from constants import CLIENTS_COUNT - - -class GameServer: - def __init__(self): - self.clients = [] - - async def connection_handler(self, client_reader, client_writer): - client = TcpClient(client_reader, client_writer) - is_success = await client.set_solution_id() - - clients_count = len(self.clients) - - if clients_count < CLIENTS_COUNT: - if is_success: - clients_count += 1 - print('{} clients connected'.format(clients_count)) - self.clients.append(client) - else: - loop.stop() - else: - client_writer.close() - - game_future = None - if clients_count == CLIENTS_COUNT: - game = Game(self.clients) - game_future = asyncio.ensure_future(game.game_loop_wrapper()) - - if game_future: - done, pending = await asyncio.wait([game_future]) - if not pending: - loop.stop() - - print('game done') - for place, player in enumerate(reversed(game.losers), 1): - print('Place {}: {}'.format(place, player.name)) - - -gs = GameServer() - -loop = asyncio.get_event_loop() -loop.run_until_complete(asyncio.start_server(gs.connection_handler, '0.0.0.0', 8000)) -try: - loop.run_forever() -finally: - loop.close() From 57204bab5380aa40f09f7f753aae19eeabb28fa6 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Thu, 18 Jul 2019 18:59:00 +0300 Subject: [PATCH 44/96] small fixes in rules --- paperio/README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/paperio/README.md b/paperio/README.md index 81deb56..1243c98 100644 --- a/paperio/README.md +++ b/paperio/README.md @@ -11,20 +11,15 @@ Решения можно присылать на любом языке программирования из списка поддерживаемых: * C++11 / .zip, .h, .cpp -* C++14 / .zip, .h, .hpp, .cpp * C++17 / .zip, .h, .hpp, .cpp * C# / .zip, .cs -* Java1.8 / .zip, .java -* Kotlin / .zip, .kt -* Haskell / .zip, .hs +* Java1.9 / .zip, .java * Go / .zip, .go * Python 2.7 / .zip, .py * Python 3.6 / .zip, .py * PHP7 / .zip, .php * Node JS / .zip, .js -* Elixir / .zip, .ex -* Rust / .zip, .rs -* Scala / .zip, .scala +* Swift / .zip, .swift Детальные инструкции по созданию своего решения, формату входных и выходных данных, сопутствующих пакетах и библиотеках можно прочитать в [разделе 2](#2-создание-решения). После того как решение было загружено и обработано, его результат можно посмотреть в визуализаторе на сайте. Попутно будут выводиться отладочный вывод и случившиеся ошибки. @@ -128,9 +123,11 @@ ```python import json import random + +config = input() # получение конфигурации игры while True: - z = input() # получение ответа от сервера + state = input() # получение тика commands = ['left', 'right', 'up', 'down'] # доступные команды cmd = random.choice(commands) # случайный выбор действия print(json.dumps({"command": cmd, 'debug': cmd}) # отправка результата From 9b12f240ecf0ade5b6d3bdf6ceb2a29e71eac13b Mon Sep 17 00:00:00 2001 From: Dmitry Sannikov Date: Thu, 18 Jul 2019 19:06:29 +0300 Subject: [PATCH 45/96] Update README.md --- paperio/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperio/README.md b/paperio/README.md index 1243c98..3b841f4 100644 --- a/paperio/README.md +++ b/paperio/README.md @@ -55,7 +55,7 @@ 1. **Игрок** - двигающийся квадрат, который управляется **ботом** участника. Положение квадрата на карте определяется координатами его центра (x, y). Скорость квадрата задается параметром SPEED и может быть на время изменена, путем взятия бонуса. -2. **Территория** - захваченная ботом область карты, на которой бот находиться в относительной безопасности. Территория может состоять из нескольких несвязанных частей. Так может получиться, например, из-за действий противников. +2. **Территория** - захваченная ботом область карты, на которой бот находится в относительной безопасности. Территория может состоять из нескольких несвязанных частей. Так может получиться, например, из-за действий противников. 3. **Шлейф** - пройденный игроком путь, вне своей территории. При возвращении игрока на свою территорию, все клетки между шлейфом и территорией, добавляются к территории игрока. При пересечении шлейфа другими игроками или при самопересечении своего шлейфа, игрок выбывает из игры, а захваченная им территория становиться нейтральной. From fef221759284a0b0f35294fe5816fc499fb461cf Mon Sep 17 00:00:00 2001 From: Dmitry Sannikov Date: Thu, 18 Jul 2019 19:26:11 +0300 Subject: [PATCH 46/96] Update README.md --- paperio/README.md | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/paperio/README.md b/paperio/README.md index 3b841f4..04d77ff 100644 --- a/paperio/README.md +++ b/paperio/README.md @@ -10,16 +10,23 @@ Решения можно присылать на любом языке программирования из списка поддерживаемых: -* C++11 / .zip, .h, .cpp -* C++17 / .zip, .h, .hpp, .cpp -* C# / .zip, .cs -* Java1.9 / .zip, .java -* Go / .zip, .go -* Python 2.7 / .zip, .py -* Python 3.6 / .zip, .py -* PHP7 / .zip, .php -* Node JS / .zip, .js -* Swift / .zip, .swift +* C# .zip,.cs +* C++11 .zip,.h,.cpp +* C++14 .zip,.h,.hpp,.cpp +* C++17 .zip,.h,.hpp,.cpp +* Elixir .zip,.ex +* Go .zip,.go +* Haskell .zip,.hs +* Java1.8 .zip,.java +* Java1.9 .zip, .java +* Kotlin .zip,.kt +* Node JS .zip,.js +* PHP7 .zip,.php +* Python 2.7 .zip,.py +* Python 3.6 .zip,.py +* Rust .zip,.rs +* Scala .zip,.scala +* Swift .zip, .swift Детальные инструкции по созданию своего решения, формату входных и выходных данных, сопутствующих пакетах и библиотеках можно прочитать в [разделе 2](#2-создание-решения). После того как решение было загружено и обработано, его результат можно посмотреть в визуализаторе на сайте. Попутно будут выводиться отладочный вывод и случившиеся ошибки. From 2302548fd4e0b7e478ea8196d37efbba0820d9c0 Mon Sep 17 00:00:00 2001 From: Dmitry Sannikov Date: Thu, 18 Jul 2019 19:26:44 +0300 Subject: [PATCH 47/96] Update README.md --- paperio/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperio/README.md b/paperio/README.md index 04d77ff..0efbf90 100644 --- a/paperio/README.md +++ b/paperio/README.md @@ -64,7 +64,7 @@ 2. **Территория** - захваченная ботом область карты, на которой бот находится в относительной безопасности. Территория может состоять из нескольких несвязанных частей. Так может получиться, например, из-за действий противников. -3. **Шлейф** - пройденный игроком путь, вне своей территории. При возвращении игрока на свою территорию, все клетки между шлейфом и территорией, добавляются к территории игрока. При пересечении шлейфа другими игроками или при самопересечении своего шлейфа, игрок выбывает из игры, а захваченная им территория становиться нейтральной. +3. **Шлейф** - пройденный игроком путь, вне своей территории. При возвращении игрока на свою территорию, все клетки между шлейфом и территорией, добавляются к территории игрока. При пересечении шлейфа другими игроками или при самопересечении своего шлейфа, игрок выбывает из игры, а захваченная им территория становится нейтральной. 4. **Бонус** - в игре имеется 3 вида бонусов - **Ускорение**, **Замедление** и **Пила**: * **Ускорение** - увеличивает скорость игрока на несколько клеток. Количество клеток может быть любым, в диапазоне от 10 до 50; From 62cd1ba871a96721169766f69525337e2fff3cb8 Mon Sep 17 00:00:00 2001 From: Dmitry Sannikov Date: Thu, 18 Jul 2019 19:50:16 +0300 Subject: [PATCH 48/96] Update README.md --- paperio/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/paperio/README.md b/paperio/README.md index 0efbf90..d77d73f 100644 --- a/paperio/README.md +++ b/paperio/README.md @@ -102,6 +102,7 @@ * При столкновении с другим игроком, проигрывает тот игрок, чей шлейф длиннее, при совпадении длины шлейфа, проигрывают оба игрока; * При пересечении границ карты; * При захвате противниками всей вашей территории; +* При попадании лучом в игрока Побеждает игрок, набравший наибольшее количество очков. From 2ae66bf4395f4107ec1c3804a97ba43c08bdab18 Mon Sep 17 00:00:00 2001 From: UZERE Date: Thu, 18 Jul 2019 23:01:29 +0300 Subject: [PATCH 49/96] Rust: edition = "2018" --- paperio/dockers/rust/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/paperio/dockers/rust/Cargo.toml b/paperio/dockers/rust/Cargo.toml index 24dedc4..81c5b6a 100644 --- a/paperio/dockers/rust/Cargo.toml +++ b/paperio/dockers/rust/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "solution" version = "0.1.0" +edition = "2018" [dependencies] chrono = "0.4.1" From e5e0b8001c9e5ec4f2776a63b1bd8950bd026e8b Mon Sep 17 00:00:00 2001 From: aantonov Date: Fri, 19 Jul 2019 03:26:24 +0300 Subject: [PATCH 50/96] Add haskell example file --- paperio/examples/haskell_strategy.hs | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 paperio/examples/haskell_strategy.hs diff --git a/paperio/examples/haskell_strategy.hs b/paperio/examples/haskell_strategy.hs new file mode 100644 index 0000000..2bf2e84 --- /dev/null +++ b/paperio/examples/haskell_strategy.hs @@ -0,0 +1,41 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Main where + +import Data.Aeson +import System.IO +import Control.Monad +import Data.Maybe +import Data.ByteString.Lazy.Char8 as Char8 +import System.Random + +data Action = Action String String +data Tick = Tick String + + +instance FromJSON Tick where + parseJSON = withObject "Tick" $ \ o -> Tick <$> o .: "type" + +instance ToJSON Action where + toJSON (Action command debug) = object ["command" .= command, "debug" .= debug] + +takeAction :: Int -> String +takeAction 0 = "left" +takeAction 1 = "right" +takeAction 2 = "up" +takeAction 3 = "down" + +myStrategy :: IO() +myStrategy = do + maybeTick <- getLine >>= pure . decode . Char8.pack :: IO (Maybe Tick) + gen <- newStdGen + let defaultTick = Tick "" + let action = takeAction $ fst $ (randomR (0, 3) gen :: (Int, StdGen)) + + Char8.putStrLn $ encode (Action action action) + +main :: IO () +main = do + hSetBuffering stdin LineBuffering + hSetBuffering stdout LineBuffering + forever myStrategy From 44155ac13b9e891819f6f3b8266b7a1a92f66f9d Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Fri, 19 Jul 2019 13:46:46 +0300 Subject: [PATCH 51/96] stest to stor --- paperio/dockers/c_sharp/Dockerfile | 2 +- paperio/dockers/cpp11/Dockerfile | 2 +- paperio/dockers/cpp17/Dockerfile | 2 +- paperio/dockers/elixir/Dockerfile | 2 +- paperio/dockers/golang/Dockerfile | 2 +- paperio/dockers/haskell/Dockerfile | 2 +- paperio/dockers/java1.8/Dockerfile | 2 +- paperio/dockers/java1.9/Dockerfile | 2 +- paperio/dockers/kotlin/Dockerfile | 2 +- paperio/dockers/nodejs9/Dockerfile | 2 +- paperio/dockers/php7/Dockerfile | 2 +- paperio/dockers/python2/Dockerfile | 2 +- paperio/dockers/python3/Dockerfile | 2 +- paperio/dockers/rust/Dockerfile | 2 +- paperio/dockers/scala/Dockerfile | 2 +- paperio/dockers/swift/Dockerfile | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/paperio/dockers/c_sharp/Dockerfile b/paperio/dockers/c_sharp/Dockerfile index 15769ae..f3394f7 100644 --- a/paperio/dockers/c_sharp/Dockerfile +++ b/paperio/dockers/c_sharp/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Boris Kolganov ENV MONO_GC_PARAMS max-heap-size=256M diff --git a/paperio/dockers/cpp11/Dockerfile b/paperio/dockers/cpp11/Dockerfile index 5b4ae2d..6267c08 100644 --- a/paperio/dockers/cpp11/Dockerfile +++ b/paperio/dockers/cpp11/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Boris Kolganov ENV COMPILED_FILE_PATH=/opt/client/a.out diff --git a/paperio/dockers/cpp17/Dockerfile b/paperio/dockers/cpp17/Dockerfile index 1af26f7..a504c20 100644 --- a/paperio/dockers/cpp17/Dockerfile +++ b/paperio/dockers/cpp17/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Boris Kolganov RUN \ diff --git a/paperio/dockers/elixir/Dockerfile b/paperio/dockers/elixir/Dockerfile index 1d132a8..f013b6a 100644 --- a/paperio/dockers/elixir/Dockerfile +++ b/paperio/dockers/elixir/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base LABEL authors="Alexey Bolshakov , Sergey Samokhvalov " COPY mix.exs /opt/client/default/mix.exs diff --git a/paperio/dockers/golang/Dockerfile b/paperio/dockers/golang/Dockerfile index 8ed62f3..4e2d08c 100644 --- a/paperio/dockers/golang/Dockerfile +++ b/paperio/dockers/golang/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Boris Kolganov RUN apt-get update -y && apt-get install --no-install-recommends -y -q curl build-essential ca-certificates git mercurial bzr diff --git a/paperio/dockers/haskell/Dockerfile b/paperio/dockers/haskell/Dockerfile index 50129bb..fa881a9 100644 --- a/paperio/dockers/haskell/Dockerfile +++ b/paperio/dockers/haskell/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Cthulhu ENV PATH=/opt/ghc/bin:$PATH diff --git a/paperio/dockers/java1.8/Dockerfile b/paperio/dockers/java1.8/Dockerfile index 40b9954..6ed039e 100644 --- a/paperio/dockers/java1.8/Dockerfile +++ b/paperio/dockers/java1.8/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Boris Kolganov RUN apt-get update -y && \ apt-get install -y maven software-properties-common curl && \ diff --git a/paperio/dockers/java1.9/Dockerfile b/paperio/dockers/java1.9/Dockerfile index 5d09329..7d8e40c 100644 --- a/paperio/dockers/java1.9/Dockerfile +++ b/paperio/dockers/java1.9/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Boris Kolganov RUN apt-get update -y && \ apt-get install -y maven && \ diff --git a/paperio/dockers/kotlin/Dockerfile b/paperio/dockers/kotlin/Dockerfile index 7aa8977..bb57a4f 100644 --- a/paperio/dockers/kotlin/Dockerfile +++ b/paperio/dockers/kotlin/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Evgeniy Zuykin RUN apt-get update -y && \ apt-get install -y software-properties-common && \ diff --git a/paperio/dockers/nodejs9/Dockerfile b/paperio/dockers/nodejs9/Dockerfile index dbe4046..ce551fa 100644 --- a/paperio/dockers/nodejs9/Dockerfile +++ b/paperio/dockers/nodejs9/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Boris Kolganov RUN curl -sL https://deb.nodesource.com/setup_9.x | bash - && \ diff --git a/paperio/dockers/php7/Dockerfile b/paperio/dockers/php7/Dockerfile index bac1f5c..d8cc7af 100644 --- a/paperio/dockers/php7/Dockerfile +++ b/paperio/dockers/php7/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Boris Kolganov RUN DEBIAN_FRONTEND=noninteractive LC_ALL=en_US.UTF-8 add-apt-repository -y ppa:ondrej/php && \ diff --git a/paperio/dockers/python2/Dockerfile b/paperio/dockers/python2/Dockerfile index 2d09982..08a13c7 100644 --- a/paperio/dockers/python2/Dockerfile +++ b/paperio/dockers/python2/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Boris Kolganov RUN apt-get update &&\ diff --git a/paperio/dockers/python3/Dockerfile b/paperio/dockers/python3/Dockerfile index b4a00f5..e530ac1 100644 --- a/paperio/dockers/python3/Dockerfile +++ b/paperio/dockers/python3/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Boris Kolganov RUN add-apt-repository ppa:jonathonf/python-3.6 && \ diff --git a/paperio/dockers/rust/Dockerfile b/paperio/dockers/rust/Dockerfile index ceaf337..567af64 100644 --- a/paperio/dockers/rust/Dockerfile +++ b/paperio/dockers/rust/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base RUN apt-get update -y && apt-get install -y build-essential curl && curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable ENV SOLUTION_CODE_PATH=/opt/client/solution \ diff --git a/paperio/dockers/scala/Dockerfile b/paperio/dockers/scala/Dockerfile index ad5ca7c..f425d83 100644 --- a/paperio/dockers/scala/Dockerfile +++ b/paperio/dockers/scala/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Konstantin Aristov WORKDIR /opt/client diff --git a/paperio/dockers/swift/Dockerfile b/paperio/dockers/swift/Dockerfile index 04c6d4c..937a783 100644 --- a/paperio/dockers/swift/Dockerfile +++ b/paperio/dockers/swift/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Boris Kolganov RUN apt-get update && \ From beef729734d1319ced0678331324903ff57c1402 Mon Sep 17 00:00:00 2001 From: Artem Zhukov Date: Sat, 20 Jul 2019 16:48:52 +0300 Subject: [PATCH 52/96] fix sprite paths in bonuses drawing --- paperio/local_runner/helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/paperio/local_runner/helpers.py b/paperio/local_runner/helpers.py index d2fbe7d..8b2a2b9 100644 --- a/paperio/local_runner/helpers.py +++ b/paperio/local_runner/helpers.py @@ -1,3 +1,4 @@ +import os import random import pyglet from constants import WIDTH, X_CELLS_COUNT, Y_CELLS_COUNT @@ -166,7 +167,9 @@ def in_polygon(x, y, xp, yp): def load_image(path): if path not in IMAGE_CACHE: - img = pyglet.image.load(path) + base_dir = os.path.dirname(os.path.realpath(__file__)) + absolute_path = os.path.join(base_dir, path) + img = pyglet.image.load(absolute_path) img.anchor_x = round(img.width / 2) img.anchor_y = round(img.height / 2) IMAGE_CACHE[path] = img From 2becdc71e88297a369102ca8513acf5fde2074aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=BE=D0=BC=D0=BE=D0=B2=D1=86=D0=B5=D0=B2=20=D0=9F?= =?UTF-8?q?=D0=B0=D0=B2=D0=B5=D0=BB?= Date: Sat, 20 Jul 2019 21:36:11 +0500 Subject: [PATCH 53/96] Play-json added More common json library --- paperio/dockers/scala/build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/paperio/dockers/scala/build.sbt b/paperio/dockers/scala/build.sbt index 729500d..8242226 100644 --- a/paperio/dockers/scala/build.sbt +++ b/paperio/dockers/scala/build.sbt @@ -8,5 +8,6 @@ libraryDependencies += "com.rojoma" %% "rojoma-json-v3" % "3.8.0" libraryDependencies += "joda-time" % "joda-time" % "2.9.9" libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3" libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.8.0" +libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4" scalacOptions in Test ++= Seq("-Yrangepos") From fcaec705cf2446470c659cca3f80b83cab9902ae Mon Sep 17 00:00:00 2001 From: VexfxjC Date: Sat, 20 Jul 2019 21:28:50 +0300 Subject: [PATCH 54/96] =?UTF-8?q?=D0=BB=D1=8E=D0=B1=D0=BE=D0=B5=20=D0=BD?= =?UTF-8?q?=D0=B0=D1=87=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B5=20=D0=BD=D0=B0?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4?= =?UTF-8?q?=D0=B2=D0=B8=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/local_runner/game_objects/player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperio/local_runner/game_objects/player.py b/paperio/local_runner/game_objects/player.py index 8228d62..b484b4c 100644 --- a/paperio/local_runner/game_objects/player.py +++ b/paperio/local_runner/game_objects/player.py @@ -7,7 +7,7 @@ class Player: speed = SPEED - direction = LEFT + direction = None def __init__(self, id, x, y, name, color, client): self.id = id From eee67cd729ee4568048387009160782f4db778c5 Mon Sep 17 00:00:00 2001 From: Sergei Fomin Date: Sun, 21 Jul 2019 16:19:34 +0300 Subject: [PATCH 55/96] Add "derive" feature to serde crate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Иначе не работают макросы Serialize, Deserialize. --- paperio/dockers/rust/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperio/dockers/rust/Cargo.toml b/paperio/dockers/rust/Cargo.toml index 24dedc4..2b989b7 100644 --- a/paperio/dockers/rust/Cargo.toml +++ b/paperio/dockers/rust/Cargo.toml @@ -10,7 +10,7 @@ lazy_static = "1.0.0" log = "0.4.1" num-traits = "0.2.2" rand = "0.4.2" -serde = "1.0.13" +serde = { version = "1.0.13", features = ["derive"] } serde_derive = "1.0.13" serde_json = "1.0.13" simplelog = "0.5.1" From 2c4c225413d6e12f7a872e4b462b1c44d58db279 Mon Sep 17 00:00:00 2001 From: VexfxjC Date: Mon, 22 Jul 2019 19:42:11 +0300 Subject: [PATCH 56/96] =?UTF-8?q?*=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=B8=D0=BD=D0=B8=D1=86=D0=B8=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D1=82=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/local_runner/game_objects/player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperio/local_runner/game_objects/player.py b/paperio/local_runner/game_objects/player.py index b484b4c..18433c0 100644 --- a/paperio/local_runner/game_objects/player.py +++ b/paperio/local_runner/game_objects/player.py @@ -7,7 +7,7 @@ class Player: speed = SPEED - direction = None + direction = 'initial' def __init__(self, id, x, y, name, color, client): self.id = id From 1d056023930307219b906497c2dfd9bc37d6f1c4 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Mon, 22 Jul 2019 23:14:54 +0300 Subject: [PATCH 57/96] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=20=D0=BF=D0=BE=D0=BD=D0=B5?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/README.md | 33 ++++------ paperio/dockers/scala/Dockerfile | 2 +- paperio/examples/java9_strategy.java | 2 +- paperio/examples/java_strategy.java | 2 +- paperio/local_runner/game_objects/bonuses.py | 2 +- paperio/local_runner/game_objects/game.py | 64 +++++++++++++------ paperio/local_runner/game_objects/player.py | 2 +- paperio/local_runner/game_objects/scene.py | 38 +++++++---- .../local_runner/game_objects/territory.py | 61 ++++++++++-------- paperio/local_runner/helpers.py | 9 +-- paperio/local_runner/localrunner.py | 13 +++- 11 files changed, 135 insertions(+), 93 deletions(-) diff --git a/paperio/README.md b/paperio/README.md index d77d73f..ccb28fd 100644 --- a/paperio/README.md +++ b/paperio/README.md @@ -10,23 +10,16 @@ Решения можно присылать на любом языке программирования из списка поддерживаемых: -* C# .zip,.cs -* C++11 .zip,.h,.cpp -* C++14 .zip,.h,.hpp,.cpp -* C++17 .zip,.h,.hpp,.cpp -* Elixir .zip,.ex -* Go .zip,.go -* Haskell .zip,.hs -* Java1.8 .zip,.java -* Java1.9 .zip, .java -* Kotlin .zip,.kt -* Node JS .zip,.js -* PHP7 .zip,.php -* Python 2.7 .zip,.py -* Python 3.6 .zip,.py -* Rust .zip,.rs -* Scala .zip,.scala -* Swift .zip, .swift +* C++11 / .zip, .h, .cpp +* C++17 / .zip, .h, .hpp, .cpp +* C# / .zip, .cs +* Java1.9 / .zip, .java +* Go / .zip, .go +* Python 2.7 / .zip, .py +* Python 3.6 / .zip, .py +* PHP7 / .zip, .php +* Node JS / .zip, .js +* Swift / .zip, .swift Детальные инструкции по созданию своего решения, формату входных и выходных данных, сопутствующих пакетах и библиотеках можно прочитать в [разделе 2](#2-создание-решения). После того как решение было загружено и обработано, его результат можно посмотреть в визуализаторе на сайте. Попутно будут выводиться отладочный вывод и случившиеся ошибки. @@ -62,9 +55,9 @@ 1. **Игрок** - двигающийся квадрат, который управляется **ботом** участника. Положение квадрата на карте определяется координатами его центра (x, y). Скорость квадрата задается параметром SPEED и может быть на время изменена, путем взятия бонуса. -2. **Территория** - захваченная ботом область карты, на которой бот находится в относительной безопасности. Территория может состоять из нескольких несвязанных частей. Так может получиться, например, из-за действий противников. +2. **Территория** - захваченная ботом область карты, на которой бот находиться в относительной безопасности. Территория может состоять из нескольких несвязанных частей. Так может получиться, например, из-за действий противников. -3. **Шлейф** - пройденный игроком путь, вне своей территории. При возвращении игрока на свою территорию, все клетки между шлейфом и территорией, добавляются к территории игрока. При пересечении шлейфа другими игроками или при самопересечении своего шлейфа, игрок выбывает из игры, а захваченная им территория становится нейтральной. +3. **Шлейф** - пройденный игроком путь, вне своей территории. При возвращении игрока на свою территорию, все клетки между шлейфом и территорией, добавляются к территории игрока. При пересечении шлейфа другими игроками или при самопересечении своего шлейфа, игрок выбывает из игры, а захваченная им территория становиться нейтральной. 4. **Бонус** - в игре имеется 3 вида бонусов - **Ускорение**, **Замедление** и **Пила**: * **Ускорение** - увеличивает скорость игрока на несколько клеток. Количество клеток может быть любым, в диапазоне от 10 до 50; @@ -102,7 +95,6 @@ * При столкновении с другим игроком, проигрывает тот игрок, чей шлейф длиннее, при совпадении длины шлейфа, проигрывают оба игрока; * При пересечении границ карты; * При захвате противниками всей вашей территории; -* При попадании лучом в игрока Побеждает игрок, набравший наибольшее количество очков. @@ -157,6 +149,7 @@ while True: * `territory` — массив координат клеток, принадлежащих территории игрока * `position` — текущее положение игрока * `lines` — массив координат клеток шлейфа + * `direction` — направление движения игрока ("left", "right", "up", "down") * `bonuses` — массив активных бонусов игрока * `type` — тип бонуса ('n' - Ускорение (Нитро), 's' - Замедление, 'saw' - Пила) * `ticks` — сколько еще клеток будет активен бонус diff --git a/paperio/dockers/scala/Dockerfile b/paperio/dockers/scala/Dockerfile index f425d83..ad5ca7c 100644 --- a/paperio/dockers/scala/Dockerfile +++ b/paperio/dockers/scala/Dockerfile @@ -1,4 +1,4 @@ -FROM stor.highloadcup.ru/aicups/paperio_base +FROM stest.tech-mail.ru/aicups/paperio_base MAINTAINER Konstantin Aristov WORKDIR /opt/client diff --git a/paperio/examples/java9_strategy.java b/paperio/examples/java9_strategy.java index a0d3f5d..a664d6d 100644 --- a/paperio/examples/java9_strategy.java +++ b/paperio/examples/java9_strategy.java @@ -14,7 +14,7 @@ public static void main(String args[]) { String[] commands = {"left", "right", "up", "down"}; Scanner scanner = new Scanner(System.in); while (true) { - String input = scanner.next(); + String input = scanner.nextLine(); String command = Main.getRandom(commands); System.out.printf("{\"command\": \"%s\"}\n", command); } diff --git a/paperio/examples/java_strategy.java b/paperio/examples/java_strategy.java index 39aa2ac..39ac646 100644 --- a/paperio/examples/java_strategy.java +++ b/paperio/examples/java_strategy.java @@ -11,7 +11,7 @@ public static void main(String args[]) { String[] commands = {"left", "right", "up", "down"}; Scanner scanner = new Scanner(System.in); while (true) { - String input = scanner.next(); + String input = scanner.nextLine(); String command = Main.getRandom(commands); System.out.printf("{\"command\": \"%s\"}\n", command); } diff --git a/paperio/local_runner/game_objects/bonuses.py b/paperio/local_runner/game_objects/bonuses.py index 3732ee6..78a08d3 100644 --- a/paperio/local_runner/game_objects/bonuses.py +++ b/paperio/local_runner/game_objects/bonuses.py @@ -36,7 +36,7 @@ def generate_coordinates(players, busy_points): return x, y def draw(self): - draw_square_with_image((self.x, self.y), self.color, self.image_path, self.active_ticks) + draw_square_with_image((self.x, self.y), self.color, self.image_path) def is_ate(self, player, captured): return (self.x, self.y) == (player.x, player.y) or (self.x, self.y) in captured diff --git a/paperio/local_runner/game_objects/game.py b/paperio/local_runner/game_objects/game.py index c54b234..39804c1 100644 --- a/paperio/local_runner/game_objects/game.py +++ b/paperio/local_runner/game_objects/game.py @@ -1,5 +1,7 @@ import os +import asyncio import json +import copy import gzip import random @@ -161,7 +163,6 @@ async def game_loop_wrapper(self, *args, **kwargs): self.send_game_start() while True: is_game_over = await self.game_loop(*args, **kwargs) - print('tick: {}'.format(self.tick)) if is_game_over or self.tick >= MAX_TICK_COUNT: self.send_game_end() self.game_save() @@ -178,14 +179,28 @@ def get_players_states(self, player=None): def get_bonuses_states(self): return [b.get_state() for b in self.bonuses] + def collision_resolution(self, players_to_captured): + res = {p: copy.copy(c) for p, c in players_to_captured.items()} + for p1, captured1 in players_to_captured.items(): + for p2, captured2 in players_to_captured.items(): + if p1 != p2: + res[p1].difference_update(captured2) + return res + + async def get_command_wrapper(self, player): + command = await player.get_command(self.tick) + if command: + player.change_direction(command) + async def game_loop(self, *args, **kwargs): self.send_game_tick() + futures = [] for player in self.players: if (player.x - round(WIDTH / 2)) % WIDTH == 0 and (player.y - round(WIDTH / 2)) % WIDTH == 0: - command = await player.get_command(self.tick) - if command: - player.change_direction(command) + futures.append(asyncio.ensure_future(self.get_command_wrapper(player))) + if futures: + await asyncio.wait(futures) for player in self.players: player.move() @@ -195,6 +210,7 @@ async def game_loop(self, *args, **kwargs): if is_loss: self.losers.append(self.players[index]) + players_to_captured = {} for player in self.players: player.remove_saw_bonus() @@ -202,10 +218,17 @@ async def game_loop(self, *args, **kwargs): player.update_lines() captured = player.territory.capture(player.lines) + players_to_captured[player] = captured if len(captured) > 0: player.lines.clear() player.score += NEUTRAL_TERRITORY_SCORE * len(captured) + + players_to_captured = self.collision_resolution(players_to_captured) + for player in self.players: + if (player.x - round(WIDTH / 2)) % WIDTH == 0 and (player.y - round(WIDTH / 2)) % WIDTH == 0: + captured = players_to_captured.get(player, set()) + player.tick_action() for bonus in self.bonuses[:]: @@ -237,10 +260,12 @@ async def game_loop(self, *args, **kwargs): 'points': removed, 'killed': False }) - for p in self.players: - if p != player: - removed = p.territory.remove_points(captured) - player.score += (ENEMY_TERRITORY_SCORE - NEUTRAL_TERRITORY_SCORE) * len(removed) + if captured: + player.territory.points.update(captured) + for p in self.players: + if p != player: + removed = p.territory.remove_points(captured) + player.score += (ENEMY_TERRITORY_SCORE - NEUTRAL_TERRITORY_SCORE) * len(removed) for player in self.losers: if player in self.players: @@ -252,7 +277,7 @@ async def game_loop(self, *args, **kwargs): return len(self.players) == 0 def save_scores(self): - d = {p.client.get_solution_id(): p.score for p in self.losers} + d = {p.client.get_solution_id(): p.score for p in self.losers + self.players} with open(self.SCORES_LOCATION, 'w') as f: f.write(json.dumps(d)) @@ -281,7 +306,7 @@ def save_visio(self): def save_debug(self): return [ - p.save_log(self.DEBUG_LOCATION) for p in self.losers + p.save_log(self.DEBUG_LOCATION) for p in self.losers + self.players ] def game_save(self): @@ -303,19 +328,19 @@ def __init__(self, clients, scene, timeout): self.scene = scene self.timeout = timeout - def show_bonuses(self): + def append_bonuses_to_leaderboard(self): for player in self.players: if len(player.bonuses) > 0: for bonus in player.bonuses: label = '{} - {} - {}'.format(player.name, bonus.name, bonus.get_remaining_ticks()) self.scene.append_label_to_leaderboard(label, player.color) - def show_losers(self): + def append_losers_to_leaderboard(self): for player in self.losers: label = '{} выбыл, результат: {}'.format(player.name, player.score) self.scene.append_label_to_leaderboard(label, player.color) - def show_score(self): + def append_scores_to_leaderboard(self): for player in self.players: label = '{} результат: {}'.format(player.name, player.score) self.scene.append_label_to_leaderboard(label, player.color) @@ -324,6 +349,12 @@ def draw_bonuses(self): for bonus in self.bonuses: bonus.draw() + def draw_leaderboard(self): + self.append_losers_to_leaderboard() + self.append_scores_to_leaderboard() + self.append_bonuses_to_leaderboard() + self.scene.draw_leaderboard() + def draw(self): for player in self.players: player.territory.draw() @@ -343,12 +374,7 @@ def draw(self): self.scene.show_game_over(timeout=True) self.draw_bonuses() - - self.scene.draw_leaderboard() - self.show_losers() - self.show_score() - self.show_bonuses() - self.scene.reset_leaderboard() + self.draw_leaderboard() async def game_loop(self, *args, **kwargs): self.scene.clear() diff --git a/paperio/local_runner/game_objects/player.py b/paperio/local_runner/game_objects/player.py index 18433c0..b484b4c 100644 --- a/paperio/local_runner/game_objects/player.py +++ b/paperio/local_runner/game_objects/player.py @@ -7,7 +7,7 @@ class Player: speed = SPEED - direction = 'initial' + direction = None def __init__(self, id, x, y, name, color, client): self.id = id diff --git a/paperio/local_runner/game_objects/scene.py b/paperio/local_runner/game_objects/scene.py index 7858586..ddaddd3 100644 --- a/paperio/local_runner/game_objects/scene.py +++ b/paperio/local_runner/game_objects/scene.py @@ -14,9 +14,16 @@ class Scene: leaderboard_height = 240 leaderboard_rows_count = 0 + labels_buffer = [] + game_over_label = pyglet.text.Label('GAME OVER', font_name='Times New Roman', + font_size=30, + color=game_over_label_color, + x=WINDOW_WIDTH / 2, y=WINDOW_HEIGHT / 2, + anchor_x='center', anchor_y='center') def __init__(self): self.window = pyglet.window.Window(height=WINDOW_HEIGHT, width=WINDOW_WIDTH) + pyglet.options['debug_gl'] = False pyglet.gl.glClearColor(*self.background_color) pyglet.gl.glEnable(pyglet.gl.GL_BLEND) pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA, pyglet.gl.GL_ONE_MINUS_SRC_ALPHA) @@ -25,25 +32,27 @@ def clear(self): self.window.clear() def append_label_to_leaderboard(self, label, color): - pyglet.text.Label(label, - font_name='Times New Roman', - font_size=16, - color=color, - x=WINDOW_WIDTH - self.leaderboard_width + 20, - y=WINDOW_HEIGHT - 20 - WIDTH / 2 - 30 * self.leaderboard_rows_count, - anchor_x='left', anchor_y='center').draw() + if len(self.labels_buffer) > self.leaderboard_rows_count: + self.labels_buffer[self.leaderboard_rows_count].text = label + self.labels_buffer[self.leaderboard_rows_count].color = color + else: + self.labels_buffer.append( + pyglet.text.Label(label, + font_name='Times New Roman', + font_size=16, + color=color, + x=WINDOW_WIDTH - self.leaderboard_width + 20, + y=WINDOW_HEIGHT - 20 - WIDTH / 2 - 30 * self.leaderboard_rows_count, + anchor_x='left', anchor_y='center') + ) self.leaderboard_rows_count += 1 def reset_leaderboard(self): self.leaderboard_rows_count = 0 def show_game_over(self, timeout=False): - label = 'TIMEOUT' if timeout else 'GAME OVER' - pyglet.text.Label(label, font_name='Times New Roman', - font_size=30, - color=self.game_over_label_color, - x=WINDOW_WIDTH / 2, y=WINDOW_HEIGHT / 2, - anchor_x='center', anchor_y='center').draw() + self.game_over_label.text = 'TIMEOUT' if timeout else 'GAME OVER' + self.game_over_label.draw() def draw_border(self): draw_line((0, 0), (0, WINDOW_HEIGHT), self.border_color) @@ -57,3 +66,6 @@ def draw_leaderboard(self): WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH - self.leaderboard_width, WINDOW_HEIGHT), self.leaderboard_color) + for label in self.labels_buffer[:self.leaderboard_rows_count]: + label.draw() + self.reset_leaderboard() diff --git a/paperio/local_runner/game_objects/territory.py b/paperio/local_runner/game_objects/territory.py index 58718e2..21cb1f8 100644 --- a/paperio/local_runner/game_objects/territory.py +++ b/paperio/local_runner/game_objects/territory.py @@ -40,35 +40,43 @@ def _capture(self, boundary): y = max_y while y > min_y: if (x, y) not in self.points and in_polygon(x, y, poligon_x_arr, poligon_y_arr): - self.points.add((x, y)) captured.append((x, y)) y -= WIDTH x -= WIDTH return captured + def is_siblings(self, p1, p2): + return p2 in get_vert_and_horiz(p1) + def get_voids_between_lines_and_territory(self, lines): boundary = self.get_boundary() voids = [] - for cur in lines: - for point in get_neighboring(cur): + for i_lp1, lp1 in enumerate(lines): + for point in get_neighboring(lp1): if point in boundary: - start_point = self.get_nearest_boundary(lines[0], boundary) - if start_point: - end_index = boundary.index(point) - start_index = boundary.index(start_point) - - try: - path = self.get_path(start_index, end_index, boundary) - except (nx.NetworkXNoPath, nx.NodeNotFound): - continue - - if len(path) > 1 and path[0] == path[-1]: - path = path[1:] - - path = [boundary[index] for index in path] - lines_path = lines[:lines.index(cur) + 1] - - voids.append(lines_path + path) + prev = None + for lp2 in lines[:i_lp1 + 1]: + start_point = self.get_nearest_boundary(lp2, boundary) + if start_point: + if prev and (self.is_siblings(prev, start_point) or prev == start_point): + prev = start_point + continue + end_index = boundary.index(point) + start_index = boundary.index(start_point) + + try: + path = self.get_path(start_index, end_index, boundary) + except (nx.NetworkXNoPath, nx.NodeNotFound): + continue + + if len(path) > 1 and path[0] == path[-1]: + path = path[1:] + + path = [boundary[index] for index in path] + lines_path = lines[lines.index(lp2):i_lp1 + 1] + + voids.append(lines_path + path) + prev = start_point return voids def capture_voids_between_lines(self, lines): @@ -77,26 +85,25 @@ def capture_voids_between_lines(self, lines): for point in get_neighboring(cur): if point in lines: end_index = lines.index(point) - path = lines[index:end_index] + path = lines[index:end_index + 1] if len(path) >= 8: captured.extend(self._capture(path)) return captured def capture(self, lines): - captured = [] + captured = set() if len(lines) > 1: if lines[-1] in self.points: voids = self.get_voids_between_lines_and_territory(lines) - captured.extend(self.capture_voids_between_lines(lines)) + captured.update(self.capture_voids_between_lines(lines)) for line in lines: if line not in self.points: - self.points.add(line) - captured.append(line) + captured.add(line) for void in voids: - captured.extend(self._capture(void)) + captured.update(self._capture(void)) if len(captured) > 0: self.changed = True return captured @@ -113,7 +120,7 @@ def remove_points(self, points): return removed def get_siblings(self, point, boundary): - return [sibling for sibling in get_vert_and_horiz(point) if sibling in boundary] + return [sibling for sibling in get_neighboring(point) if sibling in boundary] def get_path(self, start_index, end_index, boundary): graph = nx.Graph() diff --git a/paperio/local_runner/helpers.py b/paperio/local_runner/helpers.py index 8b2a2b9..ad3008b 100644 --- a/paperio/local_runner/helpers.py +++ b/paperio/local_runner/helpers.py @@ -177,7 +177,7 @@ def load_image(path): return IMAGE_CACHE[path] -def draw_square_with_image(point, color, image_path, label=None, width=WIDTH): +def draw_square_with_image(point, color, image_path, width=WIDTH): draw_square(point, color, width) x, y = point @@ -187,13 +187,6 @@ def draw_square_with_image(point, color, image_path, label=None, width=WIDTH): sprite.scale = 0.75 * (width / max(sprite.height, sprite.width)) sprite.draw() - if label is not None: - pyglet.text.Label('{}'.format(label), font_name='Times New Roman', - font_size=round(WIDTH / 7), - color=(95, 99, 104, 255), - x=x + round(WIDTH / 2), y=y + round(WIDTH / 2), - anchor_x='right', anchor_y='top').draw() - def get_random_coordinates(): x = random.randint(1, X_CELLS_COUNT) * WIDTH - round(WIDTH / 2) diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py index e285ea4..5c13a17 100644 --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -1,10 +1,11 @@ from asyncio import events import argparse +import os import pyglet from pyglet.window import key -from helpers import TERRITORY_CACHE +from helpers import TERRITORY_CACHE, load_image from clients import KeyboardClient, SimplePythonClient, FileClient from constants import LR_CLIENTS_MAX_COUNT, MAX_TICK_COUNT from game_objects.scene import Scene @@ -65,8 +66,18 @@ def on_key_release(symbol, modifiers): def stop_game(): pyglet.clock.unschedule(Runner.game_loop_wrapper) + @staticmethod + def load_sprites(): + base_dir = os.path.dirname(os.path.realpath(__file__)) + absolute_path = os.path.join(base_dir, 'sprites') + sprites = os.listdir(absolute_path) + for sprite in sprites: + if sprite.endswith('png'): + load_image('sprites/{}'.format(sprite)) + @staticmethod def run_game(): + Runner.load_sprites() Runner.game = LocalGame(clients, scene, args.timeout == 'on') Runner.game.send_game_start() pyglet.clock.schedule_interval(Runner.game_loop_wrapper, 1 / 200) From a3cf7a8faa8009eaffc640d00b2efea089d56484 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Tue, 23 Jul 2019 12:09:35 +0300 Subject: [PATCH 58/96] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=D1=8B=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=81=D0=BB=D0=B5=20=D0=BF=D0=BE=D0=BD=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/README.md | 38 +++++++++++++++++++------------- paperio/dockers/scala/Dockerfile | 2 +- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/paperio/README.md b/paperio/README.md index ccb28fd..c5a0544 100644 --- a/paperio/README.md +++ b/paperio/README.md @@ -10,16 +10,23 @@ Решения можно присылать на любом языке программирования из списка поддерживаемых: -* C++11 / .zip, .h, .cpp -* C++17 / .zip, .h, .hpp, .cpp -* C# / .zip, .cs -* Java1.9 / .zip, .java -* Go / .zip, .go -* Python 2.7 / .zip, .py -* Python 3.6 / .zip, .py -* PHP7 / .zip, .php -* Node JS / .zip, .js -* Swift / .zip, .swift +* C# .zip,.cs +* C++11 .zip,.h,.cpp +* C++14 .zip,.h,.hpp,.cpp +* C++17 .zip,.h,.hpp,.cpp +* Elixir .zip,.ex +* Go .zip,.go +* Haskell .zip,.hs +* Java1.8 .zip,.java +* Java1.9 .zip, .java +* Kotlin .zip,.kt +* Node JS .zip,.js +* PHP7 .zip,.php +* Python 2.7 .zip,.py +* Python 3.6 .zip,.py +* Rust .zip,.rs +* Scala .zip,.scala +* Swift .zip, .swift Детальные инструкции по созданию своего решения, формату входных и выходных данных, сопутствующих пакетах и библиотеках можно прочитать в [разделе 2](#2-создание-решения). После того как решение было загружено и обработано, его результат можно посмотреть в визуализаторе на сайте. Попутно будут выводиться отладочный вывод и случившиеся ошибки. @@ -30,7 +37,7 @@ С организаторами соревнования можно связаться: * в группе Telegram [@aicups](https://t.me/aicups) -* с помощью формы обратной связи на сайте соревнования   +* с помощью формы обратной связи на сайте соревнования Все числовые параметры, которые вы встретите ниже, даны как примеры. В каждой конкретной игре параметры могут быть немного изменены, что повлияет на игровой баланс и физику мира. Таким образом мы получаем более справедливый и предсказуемый рейтинг раундов. Конкретные параметры игры будут присылаться **боту** перед первым тиком (см. раздел 2). @@ -55,9 +62,9 @@ 1. **Игрок** - двигающийся квадрат, который управляется **ботом** участника. Положение квадрата на карте определяется координатами его центра (x, y). Скорость квадрата задается параметром SPEED и может быть на время изменена, путем взятия бонуса. -2. **Территория** - захваченная ботом область карты, на которой бот находиться в относительной безопасности. Территория может состоять из нескольких несвязанных частей. Так может получиться, например, из-за действий противников. +2. **Территория** - захваченная ботом область карты, на которой бот находится в относительной безопасности. Территория может состоять из нескольких несвязанных частей. Так может получиться, например, из-за действий противников. -3. **Шлейф** - пройденный игроком путь, вне своей территории. При возвращении игрока на свою территорию, все клетки между шлейфом и территорией, добавляются к территории игрока. При пересечении шлейфа другими игроками или при самопересечении своего шлейфа, игрок выбывает из игры, а захваченная им территория становиться нейтральной. +3. **Шлейф** - пройденный игроком путь, вне своей территории. При возвращении игрока на свою территорию, все клетки между шлейфом и территорией, добавляются к территории игрока. При пересечении шлейфа другими игроками или при самопересечении своего шлейфа, игрок выбывает из игры, а захваченная им территория становится нейтральной. 4. **Бонус** - в игре имеется 3 вида бонусов - **Ускорение**, **Замедление** и **Пила**: * **Ускорение** - увеличивает скорость игрока на несколько клеток. Количество клеток может быть любым, в диапазоне от 10 до 50; @@ -95,6 +102,7 @@ * При столкновении с другим игроком, проигрывает тот игрок, чей шлейф длиннее, при совпадении длины шлейфа, проигрывают оба игрока; * При пересечении границ карты; * При захвате противниками всей вашей территории; +* При попадании лучом в игрока Побеждает игрок, набравший наибольшее количество очков. @@ -136,7 +144,7 @@ while True: После старта игры, сервер-механика рассылает всем подключившимся решениям конфигурацию игры. В неё входят следующие параметры: * `type` — `start_game` * `params` — параметры игрового мира - * `x_cells_count` — количество элементарных ячеек по оси x + * `x_cells_count` — количество элементарных ячеек по оси x * `y_cells_count` — количество элементарных ячеек по оси y * `speed` — скорость игрока * `width` — ширина и высота элементарной ячейки @@ -196,4 +204,4 @@ Local Runner написан на языке программирования Pyt Пользователи Windows могут столкнуться с проблемой, когда интерпретатор языка программирования установлен не совсем верно, и его нет в системной переменной `PATH`. В таком случае необходимо указывать полный путь к интерпретатору вашего языка или добавлять его в `PATH`. -## Спасибо, что участвуете в наших чемпионатах! +## Спасибо, что участвуете в наших чемпионатах! \ No newline at end of file diff --git a/paperio/dockers/scala/Dockerfile b/paperio/dockers/scala/Dockerfile index ad5ca7c..f425d83 100644 --- a/paperio/dockers/scala/Dockerfile +++ b/paperio/dockers/scala/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Konstantin Aristov WORKDIR /opt/client From 83ada532e03802e0d79c32da0cfe76a8009d2018 Mon Sep 17 00:00:00 2001 From: Oleg Estekhin Date: Tue, 23 Jul 2019 14:05:10 +0300 Subject: [PATCH 59/96] add Java 11 docker file --- paperio/dockers/java11/Dockerfile | 17 +++++++++ paperio/dockers/java11/pom.xml | 61 +++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 paperio/dockers/java11/Dockerfile create mode 100644 paperio/dockers/java11/pom.xml diff --git a/paperio/dockers/java11/Dockerfile b/paperio/dockers/java11/Dockerfile new file mode 100644 index 0000000..cf1bc5a --- /dev/null +++ b/paperio/dockers/java11/Dockerfile @@ -0,0 +1,17 @@ +FROM stor.highloadcup.ru/aicups/paperio_base +MAINTAINER Boris Kolganov +RUN add-apt-repository -y ppa:openjdk-r/ppa && \ + apt-get update -y && \ + apt-get install -y openjdk-11-jdk && \ + apt-get install -y maven + +ENV SOLUTION_CODE_ENTRYPOINT=Main.java +ENV COMPILED_FILE_PATH=/opt/client/javaStrategy.jar +ENV SOLUTION_CODE_PATH=/opt/client/src/main/java/ +ENV COMPILATION_COMMAND='mvn package -q' +ENV RUN_COMMAND='java -jar $MOUNT_POINT' + +COPY pom.xml ./ +RUN mkdir -p src/main/java && mvn dependency:go-offline && \ + mvn package && \ + rm -rf javaStrategy.jar target/classes/ diff --git a/paperio/dockers/java11/pom.xml b/paperio/dockers/java11/pom.xml new file mode 100644 index 0000000..70b022b --- /dev/null +++ b/paperio/dockers/java11/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + JavaStrategy + JavaStrategy + 1.0 + + + UTF-8 + 11 + ${java.version} + ${java.version} + + + + javaStrategy + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + + + + maven-jar-plugin + 3.1.2 + + ${basedir} + + + Main + true + ${settings.localRepository} + repository + + + + + + + + + + org.json + json + 20180130 + + + + com.google.code.gson + gson + 2.8.5 + + + + From 4fed3270343ddbb74e7599ba3c2e7cd267b2e514 Mon Sep 17 00:00:00 2001 From: Alexandr Date: Wed, 24 Jul 2019 00:01:54 +0500 Subject: [PATCH 60/96] Update kotlin version --- paperio/dockers/kotlin/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperio/dockers/kotlin/build.gradle b/paperio/dockers/kotlin/build.gradle index a074fc8..b88448c 100644 --- a/paperio/dockers/kotlin/build.gradle +++ b/paperio/dockers/kotlin/build.gradle @@ -2,7 +2,7 @@ group 'KotlinStrategy' version '1.0' buildscript { - ext.kotlin_version = '1.2.30' + ext.kotlin_version = '1.3.41' repositories { maven { From 521b937af5b9ee458f0535263a6503d7abf5bbc6 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Wed, 24 Jul 2019 18:15:09 +0300 Subject: [PATCH 61/96] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20loclRunner-=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/local_runner/game_objects/game.py | 49 ++++++++++++--------- paperio/local_runner/game_objects/player.py | 7 +++ paperio/local_runner/game_objects/scene.py | 27 +++++++++--- paperio/local_runner/localrunner.py | 29 +++++++++++- 4 files changed, 84 insertions(+), 28 deletions(-) diff --git a/paperio/local_runner/game_objects/game.py b/paperio/local_runner/game_objects/game.py index 39804c1..216eb6d 100644 --- a/paperio/local_runner/game_objects/game.py +++ b/paperio/local_runner/game_objects/game.py @@ -14,7 +14,6 @@ class Game: - border_color = (144, 163, 174, 255) available_bonuses = [b for b in [Nitro, Slowdown, Saw] if b.visio_name in AVAILABLE_BONUSES] RESULT_LOCATION = os.environ.get('GAME_LOG_LOCATION', './result') @@ -107,9 +106,9 @@ def check_loss(self, player, players): is_loss = True for p in players: - if (p.x, p.y) in player.lines: + if (p.x, p.y) in player.lines[:-1]: if p != player: - p.score += LINE_KILL_SCORE + p.tick_score += LINE_KILL_SCORE is_loss = True for p in players: @@ -180,9 +179,10 @@ def get_bonuses_states(self): return [b.get_state() for b in self.bonuses] def collision_resolution(self, players_to_captured): - res = {p: copy.copy(c) for p, c in players_to_captured.items()} - for p1, captured1 in players_to_captured.items(): - for p2, captured2 in players_to_captured.items(): + p_to_c = {p: c for p, c in players_to_captured.items() if not p.is_ate(players_to_captured)} + res = {p: copy.copy(c) for p, c in p_to_c.items()} + for p1, captured1 in p_to_c.items(): + for p2, captured2 in p_to_c.items(): if p1 != p2: res[p1].difference_update(captured2) return res @@ -205,11 +205,6 @@ async def game_loop(self, *args, **kwargs): for player in self.players: player.move() - for index, player in enumerate(self.players): - is_loss = self.check_loss(player, self.players) - if is_loss: - self.losers.append(self.players[index]) - players_to_captured = {} for player in self.players: player.remove_saw_bonus() @@ -221,10 +216,20 @@ async def game_loop(self, *args, **kwargs): players_to_captured[player] = captured if len(captured) > 0: player.lines.clear() - player.score += NEUTRAL_TERRITORY_SCORE * len(captured) + player.tick_score += NEUTRAL_TERRITORY_SCORE * len(captured) + for player in self.players: + is_loss = self.check_loss(player, self.players) + if is_loss: + self.losers.append(player) players_to_captured = self.collision_resolution(players_to_captured) + + for player in self.players: + is_loss = player.is_ate(players_to_captured) + if is_loss: + self.losers.append(player) + for player in self.players: if (player.x - round(WIDTH / 2)) % WIDTH == 0 and (player.y - round(WIDTH / 2)) % WIDTH == 0: captured = players_to_captured.get(player, set()) @@ -248,11 +253,11 @@ async def game_loop(self, *args, **kwargs): 'loser': p.id, 'killed': True }) - player.score += SAW_KILL_SCORE + player.tick_score += SAW_KILL_SCORE else: removed = p.territory.split(line, player.direction, p) if len(removed) > 0: - player.score += SAW_SCORE + player.tick_score += SAW_SCORE Saw.append_territory(removed, p.territory.color) Saw.log.append({ 'player': player.id, @@ -265,12 +270,16 @@ async def game_loop(self, *args, **kwargs): for p in self.players: if p != player: removed = p.territory.remove_points(captured) - player.score += (ENEMY_TERRITORY_SCORE - NEUTRAL_TERRITORY_SCORE) * len(removed) + player.tick_score += (ENEMY_TERRITORY_SCORE - NEUTRAL_TERRITORY_SCORE) * len(removed) for player in self.losers: if player in self.players: self.players.remove(player) + for player in self.players: + player.score += player.tick_score + player.tick_score = 0 + self.generate_bonus() self.tick += 1 @@ -321,8 +330,6 @@ def game_save(self): class LocalGame(Game): - border_color = (144, 163, 174, 255) - def __init__(self, clients, scene, timeout): super().__init__(clients) self.scene = scene @@ -368,14 +375,16 @@ def draw(self): for player in self.players: player.draw_position() + self.scene.draw_border() + self.draw_bonuses() + # self.scene.draw_grid() + self.draw_leaderboard() + if len(self.players) == 0: self.scene.show_game_over() elif self.timeout and self.tick >= MAX_TICK_COUNT: self.scene.show_game_over(timeout=True) - self.draw_bonuses() - self.draw_leaderboard() - async def game_loop(self, *args, **kwargs): self.scene.clear() self.draw() diff --git a/paperio/local_runner/game_objects/player.py b/paperio/local_runner/game_objects/player.py index b484b4c..7561d19 100644 --- a/paperio/local_runner/game_objects/player.py +++ b/paperio/local_runner/game_objects/player.py @@ -20,6 +20,7 @@ def __init__(self, id, x, y, name, color, client): self.bonuses = [] self.name = name self.score = 0 + self.tick_score = 0 self.debug_log = [] self.client = client @@ -155,3 +156,9 @@ def get_direction_line(self): if self.direction == RIGHT: return self._get_line(WIDTH, 0) + + def is_ate(self, players_to_captured): + for p, captured in players_to_captured.items(): + if self != p and (self.x, self.y) in captured: + return True + return False diff --git a/paperio/local_runner/game_objects/scene.py b/paperio/local_runner/game_objects/scene.py index ddaddd3..5f5c24f 100644 --- a/paperio/local_runner/game_objects/scene.py +++ b/paperio/local_runner/game_objects/scene.py @@ -7,6 +7,8 @@ class Scene: background_color = (220 / 255, 240 / 255, 244 / 255, 1) border_color = (144, 163, 174, 255) + grid_color = (144, 163, 174, 64) + border_width = 2 game_over_label_color = (95, 99, 104, 255) leaderboard_color = (255, 255, 255, 128) @@ -21,8 +23,10 @@ class Scene: x=WINDOW_WIDTH / 2, y=WINDOW_HEIGHT / 2, anchor_x='center', anchor_y='center') - def __init__(self): - self.window = pyglet.window.Window(height=WINDOW_HEIGHT, width=WINDOW_WIDTH) + def __init__(self, scale): + self.window = pyglet.window.Window(height=int(WINDOW_HEIGHT * scale / 100), + width=int(WINDOW_WIDTH * scale / 100), + resizable=True) pyglet.options['debug_gl'] = False pyglet.gl.glClearColor(*self.background_color) pyglet.gl.glEnable(pyglet.gl.GL_BLEND) @@ -54,11 +58,22 @@ def show_game_over(self, timeout=False): self.game_over_label.text = 'TIMEOUT' if timeout else 'GAME OVER' self.game_over_label.draw() + def draw_grid(self): + y = WIDTH + while (y < WINDOW_HEIGHT): + draw_line((0, y), (WINDOW_WIDTH, y), self.grid_color, 2) + y += WIDTH + + x = WIDTH + while (x < WINDOW_WIDTH): + draw_line((x, 0), (x, WINDOW_HEIGHT), self.grid_color, 2) + x += WIDTH + def draw_border(self): - draw_line((0, 0), (0, WINDOW_HEIGHT), self.border_color) - draw_line((0, WINDOW_HEIGHT), (WINDOW_WIDTH, WINDOW_HEIGHT), self.border_color) - draw_line((WINDOW_WIDTH, WINDOW_HEIGHT), (WINDOW_WIDTH, 0), self.border_color) - draw_line((WINDOW_WIDTH, 0), (0, 0), self.border_color) + draw_line((0, 0), (0, WINDOW_HEIGHT), self.border_color, self.border_width) + draw_line((0, WINDOW_HEIGHT), (WINDOW_WIDTH, WINDOW_HEIGHT), self.border_color, self.border_width) + draw_line((WINDOW_WIDTH, WINDOW_HEIGHT), (WINDOW_WIDTH, 0), self.border_color, self.border_width) + draw_line((WINDOW_WIDTH, 0), (0, 0), self.border_color, self.border_width) def draw_leaderboard(self): draw_quadrilateral((WINDOW_WIDTH - self.leaderboard_width, WINDOW_HEIGHT - self.leaderboard_height, diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py index 5c13a17..b7b9809 100644 --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -3,16 +3,16 @@ import os import pyglet +from pyglet.gl import * from pyglet.window import key from helpers import TERRITORY_CACHE, load_image from clients import KeyboardClient, SimplePythonClient, FileClient -from constants import LR_CLIENTS_MAX_COUNT, MAX_TICK_COUNT +from constants import LR_CLIENTS_MAX_COUNT, MAX_TICK_COUNT, WINDOW_WIDTH, WINDOW_HEIGHT from game_objects.scene import Scene from game_objects.game import LocalGame -scene = Scene() loop = events.new_event_loop() events.set_event_loop(loop) @@ -24,9 +24,12 @@ parser.add_argument('--p{}l'.format(i), type=str, nargs='?', help='Path to log for player {}'.format(i)) parser.add_argument('-t', '--timeout', type=str, nargs='?', help='off/on timeout', default='on') +parser.add_argument('-s', '--scale', type=int, nargs='?', help='window scale (%)', default=100) args = parser.parse_args() +scene = Scene(args.scale) + clients = [] for i in range(1, LR_CLIENTS_MAX_COUNT + 1): arg = getattr(args, 'player{}'.format(i)) @@ -45,6 +48,11 @@ class Runner: + @staticmethod + def game_over_loop(dt): + Runner.game.scene.clear() + Runner.game.draw() + @staticmethod def game_loop_wrapper(dt): is_game_over = loop.run_until_complete(Runner.game.game_loop()) @@ -62,8 +70,24 @@ def on_key_release(symbol, modifiers): TERRITORY_CACHE.clear() Runner.run_game() + @staticmethod + @scene.window.event + def on_resize(width, height): + (actual_width, actual_height) = scene.window.get_viewport_size() + glViewport(0, 0, actual_width, actual_height) + glMatrixMode(gl.GL_PROJECTION) + glLoadIdentity() + + factScale = max(WINDOW_WIDTH / actual_width, WINDOW_HEIGHT / actual_height) + xMargin = (actual_width * factScale - WINDOW_WIDTH) / 2 + yMargin = (actual_height * factScale - WINDOW_HEIGHT) / 2 + glOrtho(-xMargin, WINDOW_WIDTH + xMargin, -yMargin, WINDOW_HEIGHT + yMargin, -1, 1) + glMatrixMode(gl.GL_MODELVIEW) + return pyglet.event.EVENT_HANDLED + @staticmethod def stop_game(): + pyglet.clock.schedule_interval(Runner.game_over_loop, 1 / 200) pyglet.clock.unschedule(Runner.game_loop_wrapper) @staticmethod @@ -77,6 +101,7 @@ def load_sprites(): @staticmethod def run_game(): + pyglet.clock.unschedule(Runner.game_over_loop) Runner.load_sprites() Runner.game = LocalGame(clients, scene, args.timeout == 'on') Runner.game.send_game_start() From 3d5337c4df613e9d7ded06966910f9a67026c556 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Wed, 24 Jul 2019 18:19:55 +0300 Subject: [PATCH 62/96] =?UTF-8?q?=D0=9E=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BF=D0=B8=D0=BB=D1=8F=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20Rust?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/dockers/rust/Dockerfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/paperio/dockers/rust/Dockerfile b/paperio/dockers/rust/Dockerfile index 567af64..268e024 100644 --- a/paperio/dockers/rust/Dockerfile +++ b/paperio/dockers/rust/Dockerfile @@ -1,11 +1,11 @@ -FROM stor.highloadcup.ru/aicups/paperio_base +FROM stest.tech-mail.ru/aicups/paperio_base RUN apt-get update -y && apt-get install -y build-essential curl && curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable ENV SOLUTION_CODE_PATH=/opt/client/solution \ - SOLUTION_CODE_ENTRYPOINT=src/main.rs \ - COMPILED_FILE_PATH=/opt/client/solution/target/release/solution \ - COMPILATION_COMMAND="RUSTFLAGS=-Awarnings ~/.cargo/bin/cargo build --release --bin solution --manifest-path $SOLUTION_CODE_PATH/Cargo.toml" \ - RUN_COMMAND="/lib64/ld-linux-x86-64.so.2 $MOUNT_POINT" + SOLUTION_CODE_ENTRYPOINT=src/main.rs \ + COMPILED_FILE_PATH=/opt/client/solution/target/release/solution \ + COMPILATION_COMMAND="RUSTFLAGS=-Awarnings ~/.cargo/bin/cargo build --release --bin solution --manifest-path $SOLUTION_CODE_PATH/Cargo.toml --quiet 2>&1" \ + RUN_COMMAND="/lib64/ld-linux-x86-64.so.2 $MOUNT_POINT" COPY Cargo.toml ./ RUN USER=user ~/.cargo/bin/cargo init --bin $SOLUTION_CODE_PATH &&\ From dec2598830017f5481f550de316f670518311fb7 Mon Sep 17 00:00:00 2001 From: Pavel Zubkov Date: Wed, 24 Jul 2019 23:44:11 +0700 Subject: [PATCH 63/96] Add support nodejs 12 --- paperio/dockers/nodejs12/Dockerfile | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 paperio/dockers/nodejs12/Dockerfile diff --git a/paperio/dockers/nodejs12/Dockerfile b/paperio/dockers/nodejs12/Dockerfile new file mode 100644 index 0000000..514cf37 --- /dev/null +++ b/paperio/dockers/nodejs12/Dockerfile @@ -0,0 +1,9 @@ +FROM stor.highloadcup.ru/aicups/paperio_base +MAINTAINER Boris Kolganov + +RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && \ + apt-get install -y nodejs + +ENV SOLUTION_CODE_ENTRYPOINT=main.js +ENV SOLUTION_CODE_PATH=/opt/client/solution +ENV RUN_COMMAND='/usr/bin/node $SOLUTION_CODE_PATH/$SOLUTION_CODE_ENTRYPOINT' From 79da60358a68a8613be9fc8db9901f00f91ce53a Mon Sep 17 00:00:00 2001 From: Sergei Svistunov Date: Wed, 24 Jul 2019 19:45:56 +0300 Subject: [PATCH 64/96] Update Go to 1.12.7 --- paperio/dockers/golang/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperio/dockers/golang/Dockerfile b/paperio/dockers/golang/Dockerfile index 4e2d08c..28417ed 100644 --- a/paperio/dockers/golang/Dockerfile +++ b/paperio/dockers/golang/Dockerfile @@ -2,7 +2,7 @@ FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Boris Kolganov RUN apt-get update -y && apt-get install --no-install-recommends -y -q curl build-essential ca-certificates git mercurial bzr -RUN mkdir /goroot && curl https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz | tar xvzf - -C /goroot --strip-components=1 +RUN mkdir /goroot && curl https://dl.google.com/go/go1.12.7.src.tar.gz | tar xvzf - -C /goroot --strip-components=1 RUN mkdir /gopath ENV GOROOT /goroot From c39a5c1bc42de252bc11869703cf88e40fd01b5d Mon Sep 17 00:00:00 2001 From: Oleg Estekhin Date: Wed, 24 Jul 2019 20:47:21 +0300 Subject: [PATCH 65/96] define MAVEN_OPTS with cacert properties --- paperio/dockers/java11/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/paperio/dockers/java11/Dockerfile b/paperio/dockers/java11/Dockerfile index cf1bc5a..2fe9930 100644 --- a/paperio/dockers/java11/Dockerfile +++ b/paperio/dockers/java11/Dockerfile @@ -10,6 +10,7 @@ ENV COMPILED_FILE_PATH=/opt/client/javaStrategy.jar ENV SOLUTION_CODE_PATH=/opt/client/src/main/java/ ENV COMPILATION_COMMAND='mvn package -q' ENV RUN_COMMAND='java -jar $MOUNT_POINT' +ENV MAVEN_OPTS='-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStorePassword=changeit' COPY pom.xml ./ RUN mkdir -p src/main/java && mvn dependency:go-offline && \ From de8ac2fe58ee6dd926661f6429d7f00b8391810f Mon Sep 17 00:00:00 2001 From: phpspd Date: Thu, 25 Jul 2019 10:55:46 +0300 Subject: [PATCH 66/96] Fix misprint README.md --- paperio/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paperio/README.md b/paperio/README.md index c5a0544..ccab9b3 100644 --- a/paperio/README.md +++ b/paperio/README.md @@ -187,7 +187,7 @@ Local Runner написан на языке программирования Pyt * `-h` или `--help` — показать справку * `-p1` или `--player1` — **команда для запуска исполняемого файла стратегии игрока 1 (аналогично для остальных игроков, к примеру -p3 и --player3)**, максимальное количество игроков - 6. Для игры с клавиатуры, можно в качестве значения передать строку 'keyboard'. Для запуска простой стратегии-заглушки можно передать строку 'simple_bot'. * `--p1l` — путь к логам игрока 1 (аналогично для остальных игроков, к примеру -p3l) -* `-t` или `--timeout` — по умолчанию игра прекращается после количества тиков, равного MAX_TICK_COUNT. Чтобы отключить данное поведение, передайте ранеру аругмент -t off. +* `-t` или `--timeout` — по умолчанию игра прекращается после количества тиков, равного MAX_TICK_COUNT. Чтобы отключить данное поведение, передайте ранеру аргумент -t off. Примеры запуска при установленном python3 вместе с зависимостями: * ```$ python3 localrunner.py``` — запуск игры одним игроком с управлением с клавиатуры; @@ -204,4 +204,4 @@ Local Runner написан на языке программирования Pyt Пользователи Windows могут столкнуться с проблемой, когда интерпретатор языка программирования установлен не совсем верно, и его нет в системной переменной `PATH`. В таком случае необходимо указывать полный путь к интерпретатору вашего языка или добавлять его в `PATH`. -## Спасибо, что участвуете в наших чемпионатах! \ No newline at end of file +## Спасибо, что участвуете в наших чемпионатах! From e7a05f3cd31afab2927916da9d33f1a8a6bdf523 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Thu, 25 Jul 2019 11:41:30 +0300 Subject: [PATCH 67/96] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=20=D1=81=D0=BF?= =?UTF-8?q?=D0=BE=D1=82=D1=8B=D0=BA=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BE=20?= =?UTF-8?q?=D1=88=D0=BB=D0=B5=D0=B9=D1=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/local_runner/game_objects/player.py | 35 ++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/paperio/local_runner/game_objects/player.py b/paperio/local_runner/game_objects/player.py index 7561d19..7f99153 100644 --- a/paperio/local_runner/game_objects/player.py +++ b/paperio/local_runner/game_objects/player.py @@ -8,6 +8,7 @@ class Player: speed = SPEED direction = None + prev_direction = None def __init__(self, id, x, y, name, color, client): self.id = id @@ -27,6 +28,8 @@ def __init__(self, id, x, y, name, color, client): self.is_disconnected = False def change_direction(self, command): + self.prev_direction = self.direction + if command == UP and self.direction != DOWN: self.direction = UP @@ -157,8 +160,38 @@ def get_direction_line(self): if self.direction == RIGHT: return self._get_line(WIDTH, 0) + def diff_position(self, direction, x, y, val): + if direction == UP: + return x, y - val + + if direction == DOWN: + return x, y + val + + if direction == LEFT: + return x + val, y + + if direction == RIGHT: + return x - val, y + + def get_position(self): + if self.direction is None: + return self.x, self.y + + x, y = self.x, self.y + while not ((x - round(WIDTH / 2)) % WIDTH == 0 and (y - round(WIDTH / 2)) % WIDTH == 0): + x, y = self.diff_position(self.direction, x, y, self.speed) + + return (x, y), (x, y) != (self.x, self.y) + + def get_prev_position(self): + if self.prev_direction is None: + return self.x, self.y + return self.diff_position(self.prev_direction, self.x, self.y, WIDTH) + def is_ate(self, players_to_captured): for p, captured in players_to_captured.items(): - if self != p and (self.x, self.y) in captured: + position, is_move = self.get_position() + if self != p and position in captured and \ + (is_move or self.get_prev_position() in captured): return True return False From 3678ca404d2422119974a8a3c2f31dad286468de Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Thu, 25 Jul 2019 12:17:27 +0300 Subject: [PATCH 68/96] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20kotlin=20=D0=B4=D0=BE=201.3.41?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/dockers/kotlin/Dockerfile | 5 +++-- paperio/dockers/kotlin/build.gradle | 11 ++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/paperio/dockers/kotlin/Dockerfile b/paperio/dockers/kotlin/Dockerfile index bb57a4f..ae34892 100644 --- a/paperio/dockers/kotlin/Dockerfile +++ b/paperio/dockers/kotlin/Dockerfile @@ -1,10 +1,11 @@ -FROM stor.highloadcup.ru/aicups/paperio_base +FROM stest.tech-mail.ru/aicups/paperio_base MAINTAINER Evgeniy Zuykin + RUN apt-get update -y && \ apt-get install -y software-properties-common && \ add-apt-repository -y ppa:cwchien/gradle && \ apt-get update -y && \ - apt-get install -y openjdk-8-jdk gradle-3.5.1 && \ + apt-get install -y openjdk-8-jdk gradle-5.5.1 && \ rm -rf /var/lib/apt/lists/* ENV SOLUTION_CODE_ENTRYPOINT=Main.kt diff --git a/paperio/dockers/kotlin/build.gradle b/paperio/dockers/kotlin/build.gradle index b88448c..a5d62a2 100644 --- a/paperio/dockers/kotlin/build.gradle +++ b/paperio/dockers/kotlin/build.gradle @@ -12,7 +12,7 @@ buildscript { } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.3' + classpath 'com.github.jengelman.gradle.plugins:shadow:5.1.0' } } @@ -24,8 +24,9 @@ repositories { } dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" - compile 'org.json:json:20180130' + compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + compile 'org.json:json:20180813' } sourceSets { @@ -44,4 +45,8 @@ build.dependsOn shadowJar compileKotlin { kotlinOptions.jvmTarget = "1.8" + + dependencies { + compile "org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:$kotlin_version" + } } From aaf28c631e6bf0a0e0df56c0057f765c8fc5a2c7 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Thu, 25 Jul 2019 13:21:39 +0300 Subject: [PATCH 69/96] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20go=201.12.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/dockers/golang/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paperio/dockers/golang/Dockerfile b/paperio/dockers/golang/Dockerfile index 28417ed..045893e 100644 --- a/paperio/dockers/golang/Dockerfile +++ b/paperio/dockers/golang/Dockerfile @@ -1,13 +1,13 @@ -FROM stor.highloadcup.ru/aicups/paperio_base +FROM stest.tech-mail.ru/aicups/paperio_base MAINTAINER Boris Kolganov RUN apt-get update -y && apt-get install --no-install-recommends -y -q curl build-essential ca-certificates git mercurial bzr -RUN mkdir /goroot && curl https://dl.google.com/go/go1.12.7.src.tar.gz | tar xvzf - -C /goroot --strip-components=1 +RUN mkdir /goroot && curl https://dl.google.com/go/go1.12.7.linux-amd64.tar.gz | tar xvzf - -C /goroot --strip-components=1 RUN mkdir /gopath ENV GOROOT /goroot ENV GOPATH /gopath -ENV PATH $PATH:$GOROOT/bin:$GOPATH/bin +ENV PATH $GOROOT/bin:$GOPATH/bin:$PATH RUN go install -buildmode=shared std From 7ded1e94400a4163f340e78ed1bb04c9b89adde7 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Thu, 25 Jul 2019 18:33:36 +0300 Subject: [PATCH 70/96] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D1=81=D0=B5=D1=82=D0=BA=D0=B0=20+=20?= =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D1=81=D0=BC=D0=B5=D1=80=D1=82=D0=B8?= =?UTF-8?q?=20=D0=BE=D1=82=20=D0=B8=D0=B7=D0=B1=D0=B5=D0=B3=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B7=D0=B0=D0=BA=D1=80=D0=B0=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=B8=D0=B7=D0=BD=D1=83=D1=82=D1=80=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/local_runner/game_objects/player.py | 7 ++--- paperio/local_runner/game_objects/scene.py | 34 +++++++++++++++------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/paperio/local_runner/game_objects/player.py b/paperio/local_runner/game_objects/player.py index 7f99153..440216c 100644 --- a/paperio/local_runner/game_objects/player.py +++ b/paperio/local_runner/game_objects/player.py @@ -8,7 +8,6 @@ class Player: speed = SPEED direction = None - prev_direction = None def __init__(self, id, x, y, name, color, client): self.id = id @@ -28,8 +27,6 @@ def __init__(self, id, x, y, name, color, client): self.is_disconnected = False def change_direction(self, command): - self.prev_direction = self.direction - if command == UP and self.direction != DOWN: self.direction = UP @@ -184,9 +181,9 @@ def get_position(self): return (x, y), (x, y) != (self.x, self.y) def get_prev_position(self): - if self.prev_direction is None: + if self.direction is None: return self.x, self.y - return self.diff_position(self.prev_direction, self.x, self.y, WIDTH) + return self.diff_position(self.direction, self.x, self.y, WIDTH) def is_ate(self, players_to_captured): for p, captured in players_to_captured.items(): diff --git a/paperio/local_runner/game_objects/scene.py b/paperio/local_runner/game_objects/scene.py index 5f5c24f..a5a1c7a 100644 --- a/paperio/local_runner/game_objects/scene.py +++ b/paperio/local_runner/game_objects/scene.py @@ -4,6 +4,28 @@ from helpers import draw_quadrilateral, draw_line +class Grid: + def __init__(self, color): + self.batch = pyglet.graphics.Batch() + + y = WIDTH + while (y < WINDOW_HEIGHT): + self.batch.add(2, pyglet.gl.GL_LINES, None, + ('v2i', (0, y, WINDOW_WIDTH, y)), + ('c4B', 2 * color)) + y += WIDTH + + x = WIDTH + while (x < WINDOW_WIDTH): + self.batch.add(2, pyglet.gl.GL_LINES, None, + ('v2i', (x, 0, x, WINDOW_HEIGHT)), + ('c4B', 2 * color)) + x += WIDTH + + def draw(self): + self.batch.draw() + + class Scene: background_color = (220 / 255, 240 / 255, 244 / 255, 1) border_color = (144, 163, 174, 255) @@ -31,9 +53,11 @@ def __init__(self, scale): pyglet.gl.glClearColor(*self.background_color) pyglet.gl.glEnable(pyglet.gl.GL_BLEND) pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA, pyglet.gl.GL_ONE_MINUS_SRC_ALPHA) + self.grid = Grid(self.grid_color) def clear(self): self.window.clear() + self.draw_grid() def append_label_to_leaderboard(self, label, color): if len(self.labels_buffer) > self.leaderboard_rows_count: @@ -59,15 +83,7 @@ def show_game_over(self, timeout=False): self.game_over_label.draw() def draw_grid(self): - y = WIDTH - while (y < WINDOW_HEIGHT): - draw_line((0, y), (WINDOW_WIDTH, y), self.grid_color, 2) - y += WIDTH - - x = WIDTH - while (x < WINDOW_WIDTH): - draw_line((x, 0), (x, WINDOW_HEIGHT), self.grid_color, 2) - x += WIDTH + self.grid.draw() def draw_border(self): draw_line((0, 0), (0, WINDOW_HEIGHT), self.border_color, self.border_width) From ca872752123a1e3abcc6c7722f4eb14656ae5569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9E=D0=BF=D1=8F=D0=BA=D0=B8=D0=BD=20=D0=A0=D0=BE=D0=BC?= =?UTF-8?q?=D0=B0=D0=BD?= Date: Fri, 26 Jul 2019 16:24:20 +0300 Subject: [PATCH 71/96] fix example strategy syntax in README Add round bracket to end --- paperio/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperio/README.md b/paperio/README.md index ccab9b3..5eee0a4 100644 --- a/paperio/README.md +++ b/paperio/README.md @@ -138,7 +138,7 @@ while True: state = input() # получение тика commands = ['left', 'right', 'up', 'down'] # доступные команды cmd = random.choice(commands) # случайный выбор действия - print(json.dumps({"command": cmd, 'debug': cmd}) # отправка результата + print(json.dumps({"command": cmd, 'debug': cmd})) # отправка результата ``` После старта игры, сервер-механика рассылает всем подключившимся решениям конфигурацию игры. В неё входят следующие параметры: From 1afc6921915b59a0c79b13d9fad839779cda02bf Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Sat, 27 Jul 2019 12:27:27 +0300 Subject: [PATCH 72/96] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=BB=D0=BE=D0=B3=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BC=D0=B5=D1=80=D1=82=D0=B8?= =?UTF-8?q?=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=86=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/local_runner/game_objects/game.py | 27 ++++++++++++++++++--- paperio/local_runner/game_objects/player.py | 12 +++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/paperio/local_runner/game_objects/game.py b/paperio/local_runner/game_objects/game.py index 216eb6d..3a7dbab 100644 --- a/paperio/local_runner/game_objects/game.py +++ b/paperio/local_runner/game_objects/game.py @@ -88,36 +88,54 @@ def __init__(self, clients): self.losers = [] self.bonuses = [] self.game_log = [] + self.events = [] self.tick = 1 + def append_event(self, event, p1, p2=None): + row = { + 'tick_num': self.tick, + 'event': event, + 'player': p1.get_state_for_event(), + } + if p2: + row['other'] = p2.get_state_for_event() + self.events.append(row) + def check_loss(self, player, players): is_loss = False if player.y < 0 + round(WIDTH / 2): is_loss = True + self.append_event('faced the border', player) if player.y > WINDOW_HEIGHT - round(WIDTH / 2): is_loss = True + self.append_event('faced the border', player) if player.x < 0 + round(WIDTH / 2): is_loss = True + self.append_event('faced the border', player) if player.x > WINDOW_WIDTH - round(WIDTH / 2): is_loss = True + self.append_event('faced the border', player) for p in players: if (p.x, p.y) in player.lines[:-1]: if p != player: p.tick_score += LINE_KILL_SCORE is_loss = True + self.append_event('line crossed by other player', player, p) for p in players: if is_intersect((player.x, player.y), (p.x, p.y)) and p != player: if len(player.lines) >= len(p.lines): is_loss = True + self.append_event('faced with other player', player, p) if len(player.territory.points) == 0: is_loss = True + self.append_event('has no territory', player) return is_loss @@ -134,7 +152,8 @@ def send_game_start(self): def send_game_end(self): self.game_log.append({ - 'type': 'end_game' + 'type': 'end_game', + 'events': self.events }) for player in self.players: player.send_message('end_game', {}) @@ -179,7 +198,7 @@ def get_bonuses_states(self): return [b.get_state() for b in self.bonuses] def collision_resolution(self, players_to_captured): - p_to_c = {p: c for p, c in players_to_captured.items() if not p.is_ate(players_to_captured)} + p_to_c = {p: c for p, c in players_to_captured.items() if not p.is_ate(players_to_captured)[0]} res = {p: copy.copy(c) for p, c in p_to_c.items()} for p1, captured1 in p_to_c.items(): for p2, captured2 in p_to_c.items(): @@ -226,8 +245,9 @@ async def game_loop(self, *args, **kwargs): players_to_captured = self.collision_resolution(players_to_captured) for player in self.players: - is_loss = player.is_ate(players_to_captured) + is_loss, p = player.is_ate(players_to_captured) if is_loss: + self.append_event('eaten by other player', player, p) self.losers.append(player) for player in self.players: @@ -248,6 +268,7 @@ async def game_loop(self, *args, **kwargs): if p != player: if any([is_intersect((p.x, p.y), point) for point in line]): self.losers.append(p) + self.append_event('killed by saw', p, player) Saw.log.append({ 'player': player.id, 'loser': p.id, diff --git a/paperio/local_runner/game_objects/player.py b/paperio/local_runner/game_objects/player.py index 440216c..8991ee6 100644 --- a/paperio/local_runner/game_objects/player.py +++ b/paperio/local_runner/game_objects/player.py @@ -99,6 +99,14 @@ def get_state(self): 'bonuses': self.get_bonuses_state() } + def get_state_for_event(self): + return { + 'id': self.id, + 'direction': self.direction, + 'lines_length': len(self.lines), + 'position': (self.x, self.y), + } + async def get_command(self, tick): if self.is_disconnected: return @@ -190,5 +198,5 @@ def is_ate(self, players_to_captured): position, is_move = self.get_position() if self != p and position in captured and \ (is_move or self.get_prev_position() in captured): - return True - return False + return True, p + return False, None From 03e7476b6ed6a7c5731be6b5bee79e8a362bd954 Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Sat, 27 Jul 2019 01:10:26 +0300 Subject: [PATCH 73/96] Make localrunner.py executable --- paperio/local_runner/localrunner.py | 1 + 1 file changed, 1 insertion(+) mode change 100644 => 100755 paperio/local_runner/localrunner.py diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py old mode 100644 new mode 100755 index b7b9809..befc7cf --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 from asyncio import events import argparse import os From 1915699e01ae8f7a1e00e892ba4fb189933f72a0 Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Sat, 27 Jul 2019 01:11:12 +0300 Subject: [PATCH 74/96] Fix localrunner.py -h --- paperio/local_runner/localrunner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py index befc7cf..60cc215 100755 --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -25,7 +25,7 @@ parser.add_argument('--p{}l'.format(i), type=str, nargs='?', help='Path to log for player {}'.format(i)) parser.add_argument('-t', '--timeout', type=str, nargs='?', help='off/on timeout', default='on') -parser.add_argument('-s', '--scale', type=int, nargs='?', help='window scale (%)', default=100) +parser.add_argument('-s', '--scale', type=int, nargs='?', help='window scale (%%)', default=100) args = parser.parse_args() From eea29fd04cabdca0b99fedd6cd21d8601885581f Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Sat, 27 Jul 2019 01:17:16 +0300 Subject: [PATCH 75/96] Fix missing and inconsistent data in visio.gz --- paperio/local_runner/game_objects/bonuses.py | 2 +- paperio/local_runner/game_objects/player.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/paperio/local_runner/game_objects/bonuses.py b/paperio/local_runner/game_objects/bonuses.py index 78a08d3..a70a508 100644 --- a/paperio/local_runner/game_objects/bonuses.py +++ b/paperio/local_runner/game_objects/bonuses.py @@ -48,7 +48,7 @@ def cancel(self, player): pass def get_state(self): - return {'type': self.visio_name, 'position': (self.x, self.y)} + return {'type': self.visio_name, 'position': (self.x, self.y), 'active_ticks': self.active_ticks} class Nitro(Bonus): diff --git a/paperio/local_runner/game_objects/player.py b/paperio/local_runner/game_objects/player.py index 8991ee6..7c518a0 100644 --- a/paperio/local_runner/game_objects/player.py +++ b/paperio/local_runner/game_objects/player.py @@ -93,7 +93,7 @@ def get_state(self): return { 'score': self.score, 'direction': self.direction, - 'territory': list(self.territory.points), + 'territory': list(sorted(self.territory.points)), 'lines': copy(self.lines), 'position': (self.x, self.y), 'bonuses': self.get_bonuses_state() From f70360f047845f48aeebec6af53b4326520b60b5 Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Sat, 27 Jul 2019 01:49:40 +0300 Subject: [PATCH 76/96] Add replay mode --- paperio/local_runner/localrunner.py | 85 ++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py index 60cc215..6e40fb8 100755 --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -2,16 +2,20 @@ from asyncio import events import argparse import os +import gzip +import json +import sys import pyglet from pyglet.gl import * from pyglet.window import key from helpers import TERRITORY_CACHE, load_image -from clients import KeyboardClient, SimplePythonClient, FileClient +from clients import Client, KeyboardClient, SimplePythonClient, FileClient from constants import LR_CLIENTS_MAX_COUNT, MAX_TICK_COUNT, WINDOW_WIDTH, WINDOW_HEIGHT from game_objects.scene import Scene -from game_objects.game import LocalGame +from game_objects.game import LocalGame, Game +from game_objects.bonuses import Bonus loop = events.new_event_loop() @@ -26,23 +30,74 @@ parser.add_argument('-t', '--timeout', type=str, nargs='?', help='off/on timeout', default='on') parser.add_argument('-s', '--scale', type=int, nargs='?', help='window scale (%%)', default=100) +parser.add_argument('--replay', help='Replay visio.gz (no gui)') +parser.add_argument('--no-gui', help='Disable default gui', action='store_true') args = parser.parse_args() -scene = Scene(args.scale) - -clients = [] -for i in range(1, LR_CLIENTS_MAX_COUNT + 1): - arg = getattr(args, 'player{}'.format(i)) - if arg: - if arg == 'keyboard': - client = KeyboardClient(scene.window) - elif arg == 'simple_bot': - client = SimplePythonClient() +if args.replay: + args.no_gui = True + visio = json.load(gzip.open(args.replay)) + start_game = visio['visio_info'][0] + assert(start_game['type'] == 'start_game') + # FIXME: load WIDTH, SPEED, etc from `start_game` + + BONUS_CLASSES = {bc.visio_name: bc for bc in Bonus.__subclasses__()} + org_send_game_tick = Game.send_game_tick + + def send_game_tick(self: Game): + try: + self.bonuses = [] + for b in visio['visio_info'][game.tick]['bonuses']: + bb = BONUS_CLASSES[b['type']](b['position']) + bb.active_ticks = b['active_ticks'] + self.bonuses.append(bb) + except: + pass + org_send_game_tick(self) + Game.send_game_tick = send_game_tick + + class ReplayClient(Client): + def __init__(self, id): + self.id = id + + async def get_command(self): + try: + direction = visio['visio_info'][game.tick+1]['players'][self.id]['direction'] + except: + direction = 'left' + return {"command": direction} + + def get_solution_id(self): + return visio['config'][self.id] + clients = [ReplayClient(id) for id in visio['config'].keys()] +else: + if not args.no_gui: + scene = Scene(args.scale) + clients = [] + for i in range(1, LR_CLIENTS_MAX_COUNT + 1): + arg = getattr(args, 'player{}'.format(i)) + if arg: + if arg == 'keyboard': + client = KeyboardClient(scene.window) + elif arg == 'simple_bot': + client = SimplePythonClient() + else: + client = FileClient(arg.split(), getattr(args, 'p{}l'.format(i))) + + clients.append(client) + +if args.no_gui: + game = Game(clients) + loop.run_until_complete(game.game_loop_wrapper()) + if args.replay: + for a, b in zip(visio['visio_info'], game.game_log): + if a != json.loads(json.dumps(b)): # json roundtrip to convert tuples to lists and int dict keys to strings + print("Replay '{}' failed on tick {}".format(args.replay, a.get("tick_num", None))) + sys.exit(1) else: - client = FileClient(arg.split(), getattr(args, 'p{}l'.format(i))) - - clients.append(client) + print("OK") + sys.exit(0) if len(clients) == 0: clients.append(KeyboardClient(scene.window)) From c118d36ee380600740e09ab6db0073e732240bb2 Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Sat, 27 Jul 2019 02:11:44 +0300 Subject: [PATCH 77/96] Add RewindViewer --- paperio/local_runner/RewindClient.py | 69 +++++++++++++++++++++++ paperio/local_runner/game_objects/game.py | 3 + paperio/local_runner/localrunner.py | 61 +++++++++++++++++++- 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 paperio/local_runner/RewindClient.py diff --git a/paperio/local_runner/RewindClient.py b/paperio/local_runner/RewindClient.py new file mode 100644 index 0000000..ba9d5e8 --- /dev/null +++ b/paperio/local_runner/RewindClient.py @@ -0,0 +1,69 @@ +import _socket +import json +from enum import Enum + +class RewindClient(): + def __init__(self, host=None, port=None): + self.socket = _socket.socket() + self.socket.setsockopt(_socket.IPPROTO_TCP, _socket.TCP_NODELAY, True) + if host is None: + host = "127.0.0.1" + port = 9111 + self.socket.connect((host, port)) + + def close(self): + self.socket.close() + + def _send(self, obj): + if self.socket: + self.socket.sendall(json.dumps(obj).encode('utf-8')) + + def circle(self, x, y, radius, color, layer=3): + self._send({ + 'type': 'circle', + 'x': x, + 'y': y, + 'r': radius, + 'color': color, + 'layer': layer + }) + + def rectangle(self, x1, y1, x2, y2, color, layer=3): + self._send({ + 'type': 'rectangle', + 'x1': x1, + 'y1': y1, + 'x2': x2, + 'y2': y2, + 'color': color, + 'layer': layer + }) + + def line(self, x1, y1, x2, y2, color, layer=3): + self._send({ + 'type': 'line', + 'x1': x1, + 'y1': y1, + 'x2': x2, + 'y2': y2, + 'color': color, + 'layer': layer + }) + + def message(self, msg): + self._send({ + 'type': 'message', + 'message': msg + }) + + def popup(self, x, y, r, text): + self._send({ + 'type': 'popup', + 'x': x, + 'y': y, + 'r': r, + 'text': text, + }) + + def end_frame(self): + self._send({'type': 'end'}) diff --git a/paperio/local_runner/game_objects/game.py b/paperio/local_runner/game_objects/game.py index 3a7dbab..e3bf4ba 100644 --- a/paperio/local_runner/game_objects/game.py +++ b/paperio/local_runner/game_objects/game.py @@ -221,6 +221,9 @@ async def game_loop(self, *args, **kwargs): if futures: await asyncio.wait(futures) + if hasattr(self, 'send_tick_to_rewind_viewer'): + self.send_tick_to_rewind_viewer() + for player in self.players: player.move() diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py index 6e40fb8..dabbd1f 100755 --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -12,7 +12,7 @@ from helpers import TERRITORY_CACHE, load_image from clients import Client, KeyboardClient, SimplePythonClient, FileClient -from constants import LR_CLIENTS_MAX_COUNT, MAX_TICK_COUNT, WINDOW_WIDTH, WINDOW_HEIGHT +from constants import LR_CLIENTS_MAX_COUNT, MAX_TICK_COUNT, WINDOW_WIDTH, WINDOW_HEIGHT, WIDTH from game_objects.scene import Scene from game_objects.game import LocalGame, Game from game_objects.bonuses import Bonus @@ -32,9 +32,68 @@ parser.add_argument('-s', '--scale', type=int, nargs='?', help='window scale (%%)', default=100) parser.add_argument('--replay', help='Replay visio.gz (no gui)') parser.add_argument('--no-gui', help='Disable default gui', action='store_true') +parser.add_argument('-r', '--rewind-viewer', help='RewindViewer', action='store_true') args = parser.parse_args() +if args.rewind_viewer: + from RewindClient import RewindClient + rewind_client = RewindClient() + + def send_tick_to_rewind_viewer(game: LocalGame): + W2 = WIDTH//2 + TERRITORY_LAYER = 1 + BONUS_LAYER = 2 + GRID_LAYER = 3 + TRAIL_LAYER = 4 + PLAYER_LAYER = 5 + DIRECTION_DELTA = { + 'left': (-1, 0), + 'right': (1, 0), + 'up': (0, 1), + 'down': (0, -1), + } + SPRITES = { + 'n': ((-W2+3, -W2+3, W2-3, W2-3), (0, W2-3, W2-3, W2-3), (W2-3, 0, W2-3, W2-3)), + 's': ((-W2+3, W2-3, W2-3, -W2+3), (0, -W2+3, W2-3, -W2+3), (W2-3, 0, W2-3, -W2+3)), + 'saw': ((-W2+3, 0, 0, W2-3), (0, W2-3, W2-3, 0), (W2-3, 0, 0, -W2+3), (0, -W2+3, -W2+3, 0)), + } + + def color2rv(c): + return c[2] | (c[1] << 8) | (c[0] << 16) | (c[3] << 24) + + rc = rewind_client + for x in range(32): + rc.line(x*WIDTH, 0, x*WIDTH, 31*WIDTH, 0xffffff, GRID_LAYER) + for y in range(32): + rc.line(0, y*WIDTH, 31*WIDTH, y*WIDTH, 0xffffff, GRID_LAYER) + for p in game.players: + for (x, y) in p.territory.points: + rc.rectangle(x-W2, y-W2, x+W2, y+W2, color2rv(p.territory.color), TERRITORY_LAYER) + for (x, y) in p.lines: + rc.circle(x, y, int(0.1*WIDTH), color2rv(p.line_color), TRAIL_LAYER) + rc.circle(p.x, p.y, int(0.2*WIDTH), 0, PLAYER_LAYER) + rc.circle(p.x, p.y, int(0.2*WIDTH)-2, color2rv(p.color), PLAYER_LAYER) + dx, dy = DIRECTION_DELTA.get(p.direction, (0, 0)) + rc.line(p.x, p.y, p.x+dx*int(0.2*WIDTH), p.y+dy*int(0.2*WIDTH), 0, PLAYER_LAYER) + message = [f'Tick {game.tick}'] + for p in sorted(game.players + game.losers, key=lambda p: p.id): + message += ( + '#{} @{} score {:3}'.format(p.id, '(dead )' if p in game.losers else f'({p.x:3}, {p.y:3})', p.score), + '' if not p.bonuses else ' bonus: {}'.format(', '.join(f'{b.visio_name} ({b.get_remaining_ticks()} cells)' for b in p.bonuses)), + ) + pos = int(31.5*WIDTH), int((31.5-p.id)*WIDTH) + rc.circle(*pos, int(0.4*WIDTH), color2rv(p.color), PLAYER_LAYER) + rc.popup(*pos, int(0.4*WIDTH), p.name) + for b in game.bonuses: + for dx1, dy1, dx2, dy2 in SPRITES[b.visio_name]: + rc.line(b.x+dx1, b.y+dy1, b.x+dx2, b.y+dy2, 0, BONUS_LAYER) + rc.circle(b.x, b.y, int(0.1*WIDTH), 0, BONUS_LAYER) + rc.popup(b.x, b.y, int(0.4*WIDTH), b.visio_name) + rc.message('\n'.join(message)) + rc.end_frame() + Game.send_tick_to_rewind_viewer = send_tick_to_rewind_viewer + if args.replay: args.no_gui = True visio = json.load(gzip.open(args.replay)) From 9f22d12ae037de5cc8ee451803437c3beb852846 Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Sat, 27 Jul 2019 08:12:57 +0300 Subject: [PATCH 78/96] Refactoring --- paperio/local_runner/game_objects/game.py | 10 +++++----- paperio/local_runner/localrunner.py | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/paperio/local_runner/game_objects/game.py b/paperio/local_runner/game_objects/game.py index e3bf4ba..9c1308c 100644 --- a/paperio/local_runner/game_objects/game.py +++ b/paperio/local_runner/game_objects/game.py @@ -158,7 +158,7 @@ def send_game_end(self): for player in self.players: player.send_message('end_game', {}) - def send_game_tick(self): + def append_tick_to_game_log(self): self.game_log.append({ 'type': 'tick', 'players': self.get_players_states(), @@ -167,6 +167,9 @@ def send_game_tick(self): 'saw': Saw.log }) + Saw.log = [] + + def send_game_tick(self): for player in self.players: if (player.x - round(WIDTH / 2)) % WIDTH == 0 and (player.y - round(WIDTH / 2)) % WIDTH == 0: player.send_message('tick', { @@ -175,8 +178,6 @@ def send_game_tick(self): 'tick_num': self.tick, }) - Saw.log = [] - async def game_loop_wrapper(self, *args, **kwargs): self.send_game_start() while True: @@ -221,8 +222,7 @@ async def game_loop(self, *args, **kwargs): if futures: await asyncio.wait(futures) - if hasattr(self, 'send_tick_to_rewind_viewer'): - self.send_tick_to_rewind_viewer() + self.append_tick_to_game_log() for player in self.players: player.move() diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py index dabbd1f..a0bfe81 100755 --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -40,7 +40,8 @@ from RewindClient import RewindClient rewind_client = RewindClient() - def send_tick_to_rewind_viewer(game: LocalGame): + org_append_tick_to_game_log = Game.append_tick_to_game_log + def append_tick_to_game_log(game: Game): W2 = WIDTH//2 TERRITORY_LAYER = 1 BONUS_LAYER = 2 @@ -92,7 +93,8 @@ def color2rv(c): rc.popup(b.x, b.y, int(0.4*WIDTH), b.visio_name) rc.message('\n'.join(message)) rc.end_frame() - Game.send_tick_to_rewind_viewer = send_tick_to_rewind_viewer + org_append_tick_to_game_log(game) + Game.append_tick_to_game_log = append_tick_to_game_log if args.replay: args.no_gui = True From 5fe28e94f968ea83b12c80019a19739b8ca1cf24 Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Sat, 27 Jul 2019 08:29:57 +0300 Subject: [PATCH 79/96] Refactoring --- paperio/local_runner/localrunner.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py index a0bfe81..6e2e63c 100755 --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -30,12 +30,15 @@ parser.add_argument('-t', '--timeout', type=str, nargs='?', help='off/on timeout', default='on') parser.add_argument('-s', '--scale', type=int, nargs='?', help='window scale (%%)', default=100) -parser.add_argument('--replay', help='Replay visio.gz (no gui)') +parser.add_argument('--replay', help='Replay visio.gz') parser.add_argument('--no-gui', help='Disable default gui', action='store_true') parser.add_argument('-r', '--rewind-viewer', help='RewindViewer', action='store_true') args = parser.parse_args() +if not args.no_gui: + scene = Scene(args.scale) + if args.rewind_viewer: from RewindClient import RewindClient rewind_client = RewindClient() @@ -97,19 +100,21 @@ def color2rv(c): Game.append_tick_to_game_log = append_tick_to_game_log if args.replay: - args.no_gui = True visio = json.load(gzip.open(args.replay)) start_game = visio['visio_info'][0] assert(start_game['type'] == 'start_game') # FIXME: load WIDTH, SPEED, etc from `start_game` + last_tick = 0 BONUS_CLASSES = {bc.visio_name: bc for bc in Bonus.__subclasses__()} org_send_game_tick = Game.send_game_tick def send_game_tick(self: Game): + global last_tick + last_tick = self.tick try: self.bonuses = [] - for b in visio['visio_info'][game.tick]['bonuses']: + for b in visio['visio_info'][self.tick]['bonuses']: bb = BONUS_CLASSES[b['type']](b['position']) bb.active_ticks = b['active_ticks'] self.bonuses.append(bb) @@ -124,7 +129,7 @@ def __init__(self, id): async def get_command(self): try: - direction = visio['visio_info'][game.tick+1]['players'][self.id]['direction'] + direction = visio['visio_info'][last_tick+1]['players'][self.id]['direction'] except: direction = 'left' return {"command": direction} @@ -133,8 +138,6 @@ def get_solution_id(self): return visio['config'][self.id] clients = [ReplayClient(id) for id in visio['config'].keys()] else: - if not args.no_gui: - scene = Scene(args.scale) clients = [] for i in range(1, LR_CLIENTS_MAX_COUNT + 1): arg = getattr(args, 'player{}'.format(i)) From c154de8139f8e759466346d8354cb2431a217ae4 Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Sat, 27 Jul 2019 08:36:38 +0300 Subject: [PATCH 80/96] Bump visio_version --- paperio/local_runner/game_objects/game.py | 1 + paperio/local_runner/localrunner.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/paperio/local_runner/game_objects/game.py b/paperio/local_runner/game_objects/game.py index 9c1308c..2c471f2 100644 --- a/paperio/local_runner/game_objects/game.py +++ b/paperio/local_runner/game_objects/game.py @@ -326,6 +326,7 @@ def get_players_external_id(self): def save_visio(self): d = { + 'visio_version': 2, 'config': self.get_players_external_id(), 'visio_info': self.game_log } diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py index 6e2e63c..3210cae 100755 --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -44,6 +44,7 @@ rewind_client = RewindClient() org_append_tick_to_game_log = Game.append_tick_to_game_log + def append_tick_to_game_log(game: Game): W2 = WIDTH//2 TERRITORY_LAYER = 1 @@ -101,6 +102,7 @@ def color2rv(c): if args.replay: visio = json.load(gzip.open(args.replay)) + assert(visio.get('visio_version', 0) >= 2) start_game = visio['visio_info'][0] assert(start_game['type'] == 'start_game') # FIXME: load WIDTH, SPEED, etc from `start_game` From 9e74ddd1a533fc825926875f0175a80fe2116555 Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Mon, 29 Jul 2019 01:20:54 +0300 Subject: [PATCH 81/96] Fix players order in replay --- paperio/local_runner/localrunner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py index 3210cae..8a1501d 100755 --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -131,14 +131,14 @@ def __init__(self, id): async def get_command(self): try: - direction = visio['visio_info'][last_tick+1]['players'][self.id]['direction'] + direction = visio['visio_info'][last_tick+0]['players'][self.id]['direction'] except: direction = 'left' return {"command": direction} def get_solution_id(self): return visio['config'][self.id] - clients = [ReplayClient(id) for id in visio['config'].keys()] + clients = [ReplayClient(id) for id in sorted(visio['config'].keys())] else: clients = [] for i in range(1, LR_CLIENTS_MAX_COUNT + 1): From 626429f2c39af37ce21292cdace6b3abea45dc1a Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Mon, 29 Jul 2019 01:23:51 +0300 Subject: [PATCH 82/96] Remove empty last tick from visio.gz --- paperio/local_runner/localrunner.py | 1 - 1 file changed, 1 deletion(-) diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py index 8a1501d..333d90f 100755 --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -179,7 +179,6 @@ def game_over_loop(dt): def game_loop_wrapper(dt): is_game_over = loop.run_until_complete(Runner.game.game_loop()) if is_game_over or (args.timeout == 'on' and Runner.game.tick >= MAX_TICK_COUNT): - loop.run_until_complete(Runner.game.game_loop()) Runner.game.send_game_end() Runner.game.game_save() Runner.stop_game() From dccbfd88403b31c103fa3c7ab029e3e1954ba57b Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Mon, 29 Jul 2019 09:27:29 +0300 Subject: [PATCH 83/96] Add e2e tests, TravisCI & CircleCI integration --- .circleci/config.yml | 58 ++++++++++++++++++++ .travis.yml | 10 ++++ paperio/local_runner/game_objects/game.py | 3 +- paperio/local_runner/localrunner.py | 12 ++-- paperio/local_runner/requirements.txt | 3 +- paperio/local_runner/tests/e2e/000.visio.gz | Bin 0 -> 13442 bytes paperio/local_runner/tests/e2e/001.visio.gz | Bin 0 -> 44268 bytes paperio/local_runner/tests/test_e2e.py | 8 +++ 8 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 .circleci/config.yml create mode 100644 .travis.yml create mode 100644 paperio/local_runner/tests/e2e/000.visio.gz create mode 100644 paperio/local_runner/tests/e2e/001.visio.gz create mode 100644 paperio/local_runner/tests/test_e2e.py diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..2e732ef --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,58 @@ +# Python CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-python/ for more details +# +version: 2 +jobs: + build: + docker: + # specify the version you desire here + # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers` + - image: circleci/python:3.6.1 + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/postgres:9.4 + + working_directory: ~/repo + + steps: + - checkout + + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "paperio/local_runner/requirements.txt" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - run: + name: install dependencies + command: | + python3 -m venv venv + . venv/bin/activate + pip install -r paperio/local_runner/requirements.txt + + - save_cache: + paths: + - ./venv + key: v1-dependencies-{{ checksum "paperio/local_runner/requirements.txt" }} + + # run tests! + # this example uses Django's built-in test-runner + # other common Python testing frameworks include pytest and nose + # https://pytest.org + # https://nose.readthedocs.io + - run: + name: run tests + command: | + . venv/bin/activate + cd paperio/local_runner && pytest --junit-xml=pytest/e2e.xml + + - store_artifacts: + path: paperio/local_runner/pytest + destination: test-reports + + - store_test_results: + path: paperio/local_runner/pytest diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3d969bd --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: python +cache: pip +python: + - "3.6" +# command to install dependencies +install: + - pip install -r paperio/local_runner/requirements.txt +# command to run tests +script: + - 'cd paperio/local_runner && pytest --junit-xml=pytest/e2e.xml' diff --git a/paperio/local_runner/game_objects/game.py b/paperio/local_runner/game_objects/game.py index 2c471f2..3cd379b 100644 --- a/paperio/local_runner/game_objects/game.py +++ b/paperio/local_runner/game_objects/game.py @@ -153,7 +153,8 @@ def send_game_start(self): def send_game_end(self): self.game_log.append({ 'type': 'end_game', - 'events': self.events + 'events': self.events, + 'scores': {p.client.get_solution_id(): p.score for p in self.losers + self.players} }) for player in self.players: player.send_message('end_game', {}) diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py index 333d90f..49ab49c 100755 --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -7,13 +7,10 @@ import sys import pyglet -from pyglet.gl import * -from pyglet.window import key from helpers import TERRITORY_CACHE, load_image from clients import Client, KeyboardClient, SimplePythonClient, FileClient from constants import LR_CLIENTS_MAX_COUNT, MAX_TICK_COUNT, WINDOW_WIDTH, WINDOW_HEIGHT, WIDTH -from game_objects.scene import Scene from game_objects.game import LocalGame, Game from game_objects.bonuses import Bonus @@ -37,6 +34,9 @@ args = parser.parse_args() if not args.no_gui: + from pyglet.gl import * + from pyglet.window import key + from game_objects.scene import Scene scene = Scene(args.scale) if args.rewind_viewer: @@ -158,8 +158,12 @@ def get_solution_id(self): loop.run_until_complete(game.game_loop_wrapper()) if args.replay: for a, b in zip(visio['visio_info'], game.game_log): + if a.get('type', None) == 'end_game': + a.pop('events') # ignore events + if b.get('type', None) == 'end_game': + b.pop('events') # ignore events if a != json.loads(json.dumps(b)): # json roundtrip to convert tuples to lists and int dict keys to strings - print("Replay '{}' failed on tick {}".format(args.replay, a.get("tick_num", None))) + print("Replay '{}' failed on {}:{}".format(args.replay, a.get("type", None), a.get("tick_num", None))) sys.exit(1) else: print("OK") diff --git a/paperio/local_runner/requirements.txt b/paperio/local_runner/requirements.txt index da9da28..7b61722 100644 --- a/paperio/local_runner/requirements.txt +++ b/paperio/local_runner/requirements.txt @@ -1,2 +1,3 @@ pyglet==1.3.2 -networkx==2.3 \ No newline at end of file +networkx==2.3 +pytest==5.0.1 diff --git a/paperio/local_runner/tests/e2e/000.visio.gz b/paperio/local_runner/tests/e2e/000.visio.gz new file mode 100644 index 0000000000000000000000000000000000000000..fa18ee5a46f049bfa31b1f8c92d7fa591fa7235d GIT binary patch literal 13442 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 literal 0 HcmV?d00001 diff --git a/paperio/local_runner/tests/e2e/001.visio.gz b/paperio/local_runner/tests/e2e/001.visio.gz new file mode 100644 index 0000000000000000000000000000000000000000..d7d9dad9ad609f313729f68981ccc36a27d63302 GIT binary patch literal 44268 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

cWwWlf54^InUBAE%O&gUBJ&|F6+r=w5xD( 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 literal 0 HcmV?d00001 diff --git a/paperio/local_runner/tests/test_e2e.py b/paperio/local_runner/tests/test_e2e.py new file mode 100644 index 0000000..cf8ad01 --- /dev/null +++ b/paperio/local_runner/tests/test_e2e.py @@ -0,0 +1,8 @@ +import pytest +import subprocess +import glob + +@pytest.mark.parametrize("arg", glob.glob('tests/e2e/*.visio.gz')) +def test(arg): + ret = subprocess.run(["python3", "localrunner.py", "--no-gui", "--replay", arg]) + assert 0==ret.returncode From 6f9ae6bf74f487e85627b6bb18ec46d943fc17a2 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Mon, 29 Jul 2019 18:01:09 +0300 Subject: [PATCH 84/96] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20README=20=D0=B4=D0=BB=D1=8F=20dockers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/dockers/README.md | 36 ++++++++++++++++++++++++++++++ paperio/dockers/python3/Dockerfile | 6 ++--- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 paperio/dockers/README.md diff --git a/paperio/dockers/README.md b/paperio/dockers/README.md new file mode 100644 index 0000000..229ff4e --- /dev/null +++ b/paperio/dockers/README.md @@ -0,0 +1,36 @@ +В этом каталоге расположены docker-файлы, которые будут использоваться для запуска решений. +В **каждом** docker-файле должны быть реализованы: + * Установка необходимого инструментария (размещается в секции `RUN`) для соответствующего языка (включая все необходимые библиотеки, среди которых обязательно должна быть библиотека для работы с `json`) + * Серия ENV-переменных, в которых описывается компиляция (если нужна) и запуск принятого от игрока решения (включая пути к файлам и директориям, размечаются секциями `ENV`) + * Базовая простейшая стратегия, которая просто едет к ближайшей еде (при подготовке `pull-request`-а можно взять за основу любую из уже выложенных вместе с изначальными docker-файлами) + +Каждый docker-файл предполагает, что решение участника будет лежать в недрах папки `/opt/client/` +В каждом docker-файле доступна переменная `$MOUNT_POINT`, в ней находится путь, по которому можно найти залитое решение пользователя (в момент компиляции/запуска не компилируемого языка). В момент запуска компилируемого языка там будет лежать бинарник. + +Предполагается, что авторы `pull-request`-ов будут писать docker-файл, ориентируясь на структуру уже существующих + +### Требования к docker-файлам более развернуто: + + * Желательно, на каждый файл обойтись одной `RUN`-секцией (в случае нескольких вызовов, можно элементарно обойтись `&&`, например `RUN apt-get install python python-pip && pip install sklearn`) + * Каждый docker-файл наследуется от `ubuntu:16.04` + * В каждом docker-файле должны быть определены в секциях `ENV` по меньшей мере такие переменные: + * `RUN_COMMAND` - Команда, находящаяся в этой переменной, будет исполнена для старта стратегии + * `SOLUTION_CODE_PATH` - В ней содержится путь к папке с исходниками (например, для `java` это `ENV SOLUTION_CODE_PATH=/opt/client/src/main/java/`) + * `SOLUTION_CODE_ENTRYPOINT` - Здесь содержится путь к файлу (относительно `SOLUTION_CODE_PATH`), который служит "точкой входа" в решении игрока. Для C++ это `ENV SOLUTION_CODE_ENTRYPOINT=main.cpp`, для Go это `ENV SOLUTION_CODE_ENTRYPOINT=main.go` итд + +### Прочие возможные переменные в секции `ENV` + + * `COMPILATION_COMMAND`. Будет вызвана во время компиляции (разница между `COMPILATION_COMMAND` и `RUN_COMMAND` в том, что `COMPILATION_COMMAND` вызывается один раз для присланного решения, таким образом мы экономим время и имеем возможность сразу же показать участникам ошибки компиляции) + +### Переменные по умолчанию + +Если в docker-файле явно не определены `MOUNT_POINT` и `SOLUTION_CODE_PATH`, то применяется: + * `ENV MOUNT_POINT=/opt/client/code` + * `ENV SOLUTION_CODE_PATH=/opt/client/solution` + +### На какой процесс запуска можно рассчитывать + +В соответствии в docker-файлом для того языка, на котором прислано решение, происходит ровно следующее: +1. Архив с решением разархивируется в директорию, указанную в `SOLUTION_CODE_PATH` docker-файла. Если решение пришло просто исходником, оно просто копируется в `SOLUTION_CODE_PATH`/`SOLUTION_CODE_ENTRYPOINT` +2. Если необходима стадия компиляции, то из директории `/opt/client/` вызывается команда, лежащая в `COMPILATION_COMMAND` +3. Если всё хорошо, запускается tcp-клиент `aicups`, которому отдается команда, лежащая в `RUN_COMMAND`. Tcp-клиент запускает решение игрока, просто выполняя эту команду. С этого момента для решения игрока существует лишь `STDIN`, `STDOUT` и набор библиотек, описанных в соответствующем docker-файле diff --git a/paperio/dockers/python3/Dockerfile b/paperio/dockers/python3/Dockerfile index e530ac1..df9a567 100644 --- a/paperio/dockers/python3/Dockerfile +++ b/paperio/dockers/python3/Dockerfile @@ -1,10 +1,10 @@ -FROM stor.highloadcup.ru/aicups/paperio_base +FROM stest.tech-mail.ru/aicups/paperio_base MAINTAINER Boris Kolganov RUN add-apt-repository ppa:jonathonf/python-3.6 && \ apt-get update && apt-get install -y python3.6 python3-pip && \ - python3.6 -m pip install -U numpy scipy cython scikit-learn keras pandas tensorflow==1.5.0 + python3.6 -m pip install -U numpy scipy cython scikit-learn pandas ENV SOLUTION_CODE_ENTRYPOINT=main.py ENV SOLUTION_CODE_PATH=/opt/client/solution -ENV RUN_COMMAND='python3.6 -u $SOLUTION_CODE_PATH/$SOLUTION_CODE_ENTRYPOINT' \ No newline at end of file +ENV RUN_COMMAND='python3.6 -u $SOLUTION_CODE_PATH/$SOLUTION_CODE_ENTRYPOINT' From 7b2e3243029b94e79bd3f66521f21b042b8ecd6c Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Tue, 30 Jul 2019 08:34:08 +0300 Subject: [PATCH 85/96] Add time_left. Fix #265 --- paperio/local_runner/clients.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/paperio/local_runner/clients.py b/paperio/local_runner/clients.py index 85f5b11..27e6622 100644 --- a/paperio/local_runner/clients.py +++ b/paperio/local_runner/clients.py @@ -180,7 +180,8 @@ async def set_solution_id(self): def send_message(self, t, d): msg = { 'type': t, - 'params': d + 'params': d, + 'time_left': round((self.EXECUTION_LIMIT-self.execution_time).total_seconds()*1000) } msg_bytes = '{}\n'.format(json.dumps(msg)).encode() self.writer.write(msg_bytes) From 6728f5e9fe8259d38cfe0b2736fb44d251b96ad8 Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Tue, 30 Jul 2019 08:40:51 +0300 Subject: [PATCH 86/96] Update cpp17 image to g++9 --- paperio/dockers/cpp17/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperio/dockers/cpp17/Dockerfile b/paperio/dockers/cpp17/Dockerfile index a504c20..5b348d8 100644 --- a/paperio/dockers/cpp17/Dockerfile +++ b/paperio/dockers/cpp17/Dockerfile @@ -6,7 +6,7 @@ RUN \ apt-get install -y software-properties-common && \ add-apt-repository ppa:ubuntu-toolchain-r/test -y && \ apt-get update -y && \ - apt-get install -y g++-7 make cmake + apt-get install -y g++-9 make cmake COPY Makefile ./ COPY ./nlohmann ./nlohmann From 158abc80775a82b474a525c18d9cd55fdc1b948f Mon Sep 17 00:00:00 2001 From: Boris Zaitsev Date: Tue, 30 Jul 2019 16:49:46 +0500 Subject: [PATCH 87/96] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=20=D1=81=D0=BC?= =?UTF-8?q?=D0=B5=D1=80=D1=82=D1=8C=20=D0=BD=D0=B0=20=D1=81=D0=B2=D0=BE?= =?UTF-8?q?=D0=B5=D0=B9=20=D1=82=D0=B5=D1=80=D1=80=D0=B8=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В случае, если у игрока шлейф нулевой длины, то есть он находится на своей территории, то пропускается проверка коллизий с другими игроками. Это означает, что игрок не погибнет от столкновений, пока он не отойдет на 30 мини-ячеек от своей территории. --- paperio/local_runner/game_objects/game.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/paperio/local_runner/game_objects/game.py b/paperio/local_runner/game_objects/game.py index 3a7dbab..c467667 100644 --- a/paperio/local_runner/game_objects/game.py +++ b/paperio/local_runner/game_objects/game.py @@ -127,11 +127,13 @@ def check_loss(self, player, players): is_loss = True self.append_event('line crossed by other player', player, p) - for p in players: - if is_intersect((player.x, player.y), (p.x, p.y)) and p != player: - if len(player.lines) >= len(p.lines): - is_loss = True - self.append_event('faced with other player', player, p) + if len(player.lines) > 0: + for p in players: + if is_intersect((player.x, player.y), (p.x, p.y)) and p != player: + if len(player.lines) >= len(p.lines): + is_loss = True + self.append_event('faced with other player', player, p) + break if len(player.territory.points) == 0: is_loss = True From fbe6ecdec36152af601dd506c5f124b64c889c98 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Wed, 31 Jul 2019 14:40:15 +0300 Subject: [PATCH 88/96] =?UTF-8?q?=D0=A7=D1=83=D1=82=D1=8C=20=D1=82=D0=BE?= =?UTF-8?q?=D1=87=D0=BD=D0=B5=D0=B5=20=D1=84=D0=BE=D1=80=D0=BC=D1=83=D0=BB?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/README.md | 2 +- paperio/dockers/python3/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/paperio/README.md b/paperio/README.md index ccab9b3..37d7bb3 100644 --- a/paperio/README.md +++ b/paperio/README.md @@ -100,7 +100,7 @@ Причины выбывания игрока из игры: * При пересечении шлейфа другими игроками или при самопересечении своего шлейфа; * При столкновении с другим игроком, проигрывает тот игрок, чей шлейф длиннее, при совпадении длины шлейфа, проигрывают оба игрока; -* При пересечении границ карты; +* При пересечении границ карты (с учётом размеров игрока); * При захвате противниками всей вашей территории; * При попадании лучом в игрока diff --git a/paperio/dockers/python3/Dockerfile b/paperio/dockers/python3/Dockerfile index df9a567..6d28fec 100644 --- a/paperio/dockers/python3/Dockerfile +++ b/paperio/dockers/python3/Dockerfile @@ -1,4 +1,4 @@ -FROM stest.tech-mail.ru/aicups/paperio_base +FROM stor.highloadcup.ru/aicups/paperio_base MAINTAINER Boris Kolganov RUN add-apt-repository ppa:jonathonf/python-3.6 && \ From 65492af3905ad17b3bdabed7c5accb1bd1806aef Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Wed, 31 Jul 2019 20:26:48 +0300 Subject: [PATCH 89/96] Update cpp17 image Makefile to g++9 --- paperio/dockers/cpp17/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperio/dockers/cpp17/Makefile b/paperio/dockers/cpp17/Makefile index 7ccffdb..673afec 100644 --- a/paperio/dockers/cpp17/Makefile +++ b/paperio/dockers/cpp17/Makefile @@ -1,5 +1,5 @@ CXXFLAGS=-std=c++17 -O3 -m64 -pipe -w -pthread -CXX=g++-7 +CXX=g++-9 SRCS = $(shell find ${SOLUTION_CODE_PATH} -type f -name '*.cpp') From 06bfb9a7a8c56e0c9e091e7317ba0d6754bb38e0 Mon Sep 17 00:00:00 2001 From: Alex Shumsky Date: Thu, 1 Aug 2019 09:18:40 +0300 Subject: [PATCH 90/96] Fix timeout issue --- paperio/dockers/base/sources/tcp_client.h | 38 +++++++++++++---------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/paperio/dockers/base/sources/tcp_client.h b/paperio/dockers/base/sources/tcp_client.h index 2c3cd49..7be119c 100644 --- a/paperio/dockers/base/sources/tcp_client.h +++ b/paperio/dockers/base/sources/tcp_client.h @@ -75,17 +75,19 @@ private slots: } void on_ready_read() { - QByteArray &&data = socket->readLine(0); - qDebug() << data; -// qDebug() << "Data:" << data; - if (first_read) { - solution->start(solution_run); - first_read = false; - } - int written = solution->write(data); - // qDebug() << "Written" << written; - if (written == -1) { - qDebug() << "Can not write to solution"; + while (socket->canReadLine()) { + QByteArray &&data = socket->readLine(0); + qDebug() << data; + // qDebug() << "Data:" << data; + if (first_read) { + solution->start(solution_run); + first_read = false; + } + int written = solution->write(data); + // qDebug() << "Written" << written; + if (written == -1) { + qDebug() << "Can not write to solution"; + } } } @@ -98,12 +100,14 @@ private slots: public slots: void on_solution_out() { - QByteArray &&cmd = solution->readLine(MAX_RESP_LEN); - // qDebug() << cmd; - - int sent = socket->write(cmd); - if (sent == -1) { - qDebug() << "Can not send command"; + while (solution->canReadLine()) { + QByteArray &&cmd = solution->readLine(MAX_RESP_LEN); + // qDebug() << cmd; + + int sent = socket->write(cmd); + if (sent == -1) { + qDebug() << "Can not send command"; + } } } From 7e002aae82488ab0ac516dcd1c60dafb181a21fe Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 1 Aug 2019 13:29:17 +0300 Subject: [PATCH 91/96] Collision Doc --- paperio/COLLISIONS.md | 59 ++++++++++++++++++++++++++ paperio/videos/empty_hole.gif | Bin 0 -> 109696 bytes paperio/videos/empty_hole_2.gif | Bin 0 -> 103471 bytes paperio/videos/intersect_attempt.gif | Bin 0 -> 143232 bytes paperio/videos/intersect_death.gif | Bin 0 -> 58472 bytes paperio/videos/paint_death.gif | Bin 0 -> 101459 bytes paperio/videos/paint_death_ext.gif | Bin 0 -> 104745 bytes paperio/videos/paint_inside.gif | Bin 0 -> 95713 bytes paperio/videos/paint_vs_intersect.gif | Bin 0 -> 102655 bytes 9 files changed, 59 insertions(+) create mode 100755 paperio/COLLISIONS.md create mode 100755 paperio/videos/empty_hole.gif create mode 100755 paperio/videos/empty_hole_2.gif create mode 100755 paperio/videos/intersect_attempt.gif create mode 100755 paperio/videos/intersect_death.gif create mode 100755 paperio/videos/paint_death.gif create mode 100755 paperio/videos/paint_death_ext.gif create mode 100755 paperio/videos/paint_inside.gif create mode 100755 paperio/videos/paint_vs_intersect.gif diff --git a/paperio/COLLISIONS.md b/paperio/COLLISIONS.md new file mode 100755 index 0000000..9eb984e --- /dev/null +++ b/paperio/COLLISIONS.md @@ -0,0 +1,59 @@ +## Граничные случаи + +В данном документе описаны граничные случаи в игре Paper IO (соревнование AiCups #4). + + +#### 1. Одновременное пересечение шлейфа + +* **Что происходит?** Два и более игрока одновременно (то есть, в один и тот же тик) догоняют чужие шлейфы и пересекают их. +* **Что в итоге?** Все, чей шлейф был пересечён, умирают. За тик со смертью игрокам не начисляют баллов (поэтому у обоих по нулю баллов). +* **Как это выглядит?** + +![Видео 1](videos/intersect_death.gif) + + +#### 2. Попытка закрасить одну и ту же территорию + +* **Что происходит?** Два и более игрока одновременно (то есть, в один и тот же тик) закрашивают одну и ту же территорию. Причём ни один из игроков на данной территории не находится и в конце все остаются в живых. +* **Что в итоге?** Территория, на которую в один и тот же тик претендуют несколько игроков, остаётся ничейной. При этом баллы за неё получат оба, как будто у обоих получилось её закрасить. +* **Как это выглядит?** + +![Видео 2](videos/empty_hole.gif) +![Видео 2.1](videos/empty_hole_2.gif) + + +#### 3. Убийство закрашиванием + +* **Что происходит?** Первый игрок (зелёный) поглощает клетку, на которой в данный момент стоит второй игрок (красный). Наличие у второго игрока внешней территории (то есть, не подпадающей под закраску) не влияет на исход. +* **Что в итоге?** Закрашенный игрок умирает, а тот кто закрасил получает очки и за убийство и за закрашенную территорию. +* **Как это выглядит?** + +![Видео 3](videos/paint_death.gif) +![Видео 3.1](videos/paint_death_ext.gif) + + +#### 4. Попытка закраски внутри чужой закраски + +* **Что происходит?** Два игрока одновременно закрашивают одну и ту же территорию. Причём один из игроков (красный) оказался на закрашиваемой территории другого (зелёного). +* **Что в итоге?** Игрок, который в момент чужой закраски оказался на ней, погибает, даже если в этот момент закрашивает сам. При этом умерший не получает ни новой территории ни баллов и никакие клетки не считаются спорными (то есть, отходят закрасившему игроку). +* **Как это выглядит?** + +![Видео 4](videos/paint_inside.gif) + + +#### 5. Попытка выжить, пересекая шлейф + +* **Что происходит?** Первый игрок (зелёный) пытается "поймать" второго игрока (красного) в свою закрашиваемую область. Второй пытается из неё "удрать" и в момент закраски пересекает шлейф противника. +* **Что в итоге?** Закраска в данном случае считается более приоритетной, чем пересечение шлейфа. Поэтому "пойманный" игрок погибает, и считается, что шлейф он не пересёк. +* **Как это выглядит?** + +![Видео 5](videos/intersect_attempt.gif) + + +#### 6. Попытка пересечь шлейф во время чужой закраски + +* **Что происходит?** Первый (красный) игрок занимается захватом территории. Второй игрок (зелёный) захотел на него "напасть" и пересечь его шлейф. Затем происходит одновременное пересечение шлейфа и закраска области. В отличие от случая 5, пересечение шлейфа происходит извне закрашиваемой области. +* **Что в итоге?** Захват области первым игроком проходит успешно. Считается, что шлейф пересечь не успели, поэтому второй игрок продолжит движение по закрашенной противником области. +* **Как это выглядит?** + +![Видео 6](videos/paint_vs_intersect.gif) diff --git a/paperio/videos/empty_hole.gif b/paperio/videos/empty_hole.gif new file mode 100755 index 0000000000000000000000000000000000000000..3910a32fb6bf77011221baf1fb1d206721ddb2ba GIT binary patch literal 109696 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�zZ_~nuew(=F*QfR5 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ɗi-K)b1Sy{9n1uj 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 JN^;PN{{og+mR0}& literal 0 HcmV?d00001 diff --git a/paperio/videos/empty_hole_2.gif b/paperio/videos/empty_hole_2.gif new file mode 100755 index 0000000000000000000000000000000000000000..9d54e638c75ced2a62125dafcd400f50538be108 GIT binary patch literal 103471 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 G!5RQ6qy0tz literal 0 HcmV?d00001 diff --git a/paperio/videos/intersect_attempt.gif b/paperio/videos/intersect_attempt.gif new file mode 100755 index 0000000000000000000000000000000000000000..c2bb14576bf63e8b5441fbbbbd587bd4d0e5973b GIT binary patch literal 143232 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

fN6EuWq39s)Q1 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 z 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 X&Fk>L&&B(H-3R_}+Wr4{9)JBC>_H&L literal 0 HcmV?d00001 diff --git a/paperio/videos/paint_death.gif b/paperio/videos/paint_death.gif new file mode 100755 index 0000000000000000000000000000000000000000..1c3ededbf7b615d3b47e82915eed7163680f4417 GIT binary patch literal 101459 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 z

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%0Aqt!`SLx&*w!8%I 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 a>P3<6km^l^p)qhJdo4TVI|szwivAa9HcuD; literal 0 HcmV?d00001 diff --git a/paperio/videos/paint_inside.gif b/paperio/videos/paint_inside.gif new file mode 100755 index 0000000000000000000000000000000000000000..531c4531020e7d2c36c82c6c3259543e58bd3b74 GIT binary patch literal 95713 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�z~+>(7OCQZqbf-29QF(#)+ORI$;O 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 literal 0 HcmV?d00001 From 244527fef7800a2c6f02d4c4ec5a98fc86f378bc Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 1 Aug 2019 14:17:28 +0300 Subject: [PATCH 92/96] Collision examples bots --- .../examples_scripted_1.py | 90 +++++++++++++++++++ .../examples_scripted_2.py | 80 +++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 paperio/examples/collisions_examples/examples_scripted_1.py create mode 100644 paperio/examples/collisions_examples/examples_scripted_2.py diff --git a/paperio/examples/collisions_examples/examples_scripted_1.py b/paperio/examples/collisions_examples/examples_scripted_1.py new file mode 100644 index 0000000..0366852 --- /dev/null +++ b/paperio/examples/collisions_examples/examples_scripted_1.py @@ -0,0 +1,90 @@ +import json + +# death by intersect +SCRIPT_COLL_1 = [ + 'down', + 'right', 'right', 'right', 'right', 'right', 'right', + 'up', 'up', 'up', + 'left', +] + +# empty hole +SCRIPT_COLL_2 = [ + 'down', + 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', + 'up', 'up', 'up', + 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', + 'down', +] + +# paint death +SCRIPT_COLL_3 = [ + 'down', 'down', + 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', + 'up', 'up', 'up', 'up', + 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', + 'down', 'down', +] + +# paint death ext +SCRIPT_COLL_3_EXT = [ + 'down', 'down', + 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', + 'up', 'up', 'up', 'up', + 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', + 'down', 'down', +] + +# paint inside +SCRIPT_COLL_4 = [ + 'down', 'down', + 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', + 'up', 'up', 'up', 'up', + 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', + 'down', +] + +# intersect attempt +SCRIPT_COLL_5 = [ + 'down', 'down', 'down', + 'right', 'right', 'right', 'right', 'right', 'right', + 'up', 'up', 'up', 'up', 'up', 'up', + 'left', 'left', 'left', 'left', 'left', + 'down', 'down', 'down', 'down', 'down', 'down', 'down', + 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', + 'up', 'up', 'up', 'up', 'up', 'up', 'up', + 'left', 'left', 'left', 'left', 'left', 'left', +] + +# paint vs intersect +SCRIPT_COLL_6 = [ + 'up', 'right', 'right', 'down', 'down', 'left', + 'up', 'right', 'right', 'down', 'down', 'down', 'down', 'down', 'down', 'left', 'up', 'up', 'up', 'up', + 'up', 'right', 'right', 'right', 'right', 'down', +] + +SCRIPT_COLL_7 = [ + 'right', + 'down', + 'right', 'right', 'right', + 'up', 'up', 'up', 'up', 'up', + 'left', + 'down', 'down', 'down', 'down', + 'left', + 'up', 'up', 'up', 'up', + 'left', + 'down', 'down', + 'left', + 'down', 'down' +] + + +def main(commands): + i = 0 + while True: + z = input() + print(json.dumps({"command": commands[i], 'debug': str(z)})) + i += 1 + + +main(SCRIPT_COLL_3_EXT) diff --git a/paperio/examples/collisions_examples/examples_scripted_2.py b/paperio/examples/collisions_examples/examples_scripted_2.py new file mode 100644 index 0000000..f4143b7 --- /dev/null +++ b/paperio/examples/collisions_examples/examples_scripted_2.py @@ -0,0 +1,80 @@ +import json + +# death by intersect +SCRIPT_COLL_1 = [ + 'up', + 'left', 'left', 'left', 'left', 'left', 'left', + 'down', 'down', 'down', + 'right', +] + +# empty hole +SCRIPT_COLL_2 = [ + 'up', + 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', + 'down', 'down', 'down', + 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', + 'up', +] + +# paint death +SCRIPT_COLL_3 = [ + 'up', 'left', 'down', 'down', 'right', 'right', + 'up', 'up', 'left', 'left', 'down', 'down', 'right', 'right', + 'up', 'up', 'left', 'left', 'down', 'down', 'right', 'right', + 'up', 'up', 'left', 'left', 'down', 'down', 'right', 'right', + 'up', 'up', 'left', 'left', 'down', 'down', 'right', 'right', +] + +# paint death ext +SCRIPT_COLL_3_EXT = [ + 'down', 'down', 'down', 'down', 'right', 'up', 'up', 'up', + 'up', 'up', 'left', 'left', 'down', 'down', 'right', 'right', + 'up', 'up', 'left', 'left', 'down', 'down', 'right', 'right', + 'up', 'up', 'left', 'left', 'down', 'down', 'right', 'right', + 'up', 'up', 'left', 'left', 'down', 'down', 'right', 'right', +] + +# paint inside +SCRIPT_COLL_4 = [ + 'up', + 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', + 'down', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', + 'down', 'left', 'left', 'left', 'left', 'left', 'left', 'left', 'left', + 'up', 'right', +] + +# intersect attempt +SCRIPT_COLL_5 = [ + 'up', 'left', 'down', 'down', 'right', 'right', + 'up', 'up', 'left', 'left', 'down', 'down', 'down', 'right', 'right', 'right', + 'up', 'up', 'up', 'left', 'left', 'left', 'down', 'down', 'down', 'right', 'right', 'right', + 'up', 'up', 'up', 'left', 'up', 'left', 'left', 'left', 'left', 'left', 'left', 'left', + 'down', 'down', 'down', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right', +] + +# paint vs intersect +SCRIPT_COLL_6 = [ + 'down', 'down', 'left', 'left', 'left', 'up', 'up', 'up', 'right', 'right', + 'down', 'down', 'left', 'left', 'left', 'left', 'up', 'up', 'up', 'up', 'right', 'right', 'down', 'down', 'left', +] + +SCRIPT_COLL_7 = [ + 'up', 'up', 'up', + 'right', 'right', 'right', 'right', 'right', + 'down', 'down', 'down', 'down', 'down', 'down', 'down', + 'left', 'left','left', 'left', 'left', 'left', + 'up', 'up', 'up', 'up', + 'right', +] + + +def main(commands): + i = 0 + while True: + z = input() + print(json.dumps({"command": commands[i], 'debug': str(z)})) + i += 1 + + +main(SCRIPT_COLL_3_EXT) From 92d611ec81dbbf9d097e73f4f6d86e4be5dc4504 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Wed, 7 Aug 2019 17:16:10 +0300 Subject: [PATCH 93/96] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=D1=82=20=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=87=D0=B8=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/local_runner/RewindClient.py | 69 ---------- paperio/local_runner/clients.py | 37 +++-- paperio/local_runner/constants.py | 75 ++++++----- paperio/local_runner/game_objects/bonuses.py | 16 +-- paperio/local_runner/game_objects/game.py | 96 +++++++------ paperio/local_runner/game_objects/player.py | 60 ++++----- paperio/local_runner/game_objects/scene.py | 53 ++++---- .../local_runner/game_objects/territory.py | 32 +++-- paperio/local_runner/helpers.py | 60 +++++---- paperio/local_runner/localrunner.py | 126 ++++-------------- paperio/local_runner/requirements.txt | 3 +- paperio/local_runner/tests/e2e/000.visio.gz | Bin 13442 -> 0 bytes paperio/local_runner/tests/e2e/001.visio.gz | Bin 44268 -> 0 bytes paperio/local_runner/tests/test_e2e.py | 8 -- 14 files changed, 245 insertions(+), 390 deletions(-) delete mode 100644 paperio/local_runner/RewindClient.py mode change 100755 => 100644 paperio/local_runner/localrunner.py delete mode 100644 paperio/local_runner/tests/e2e/000.visio.gz delete mode 100644 paperio/local_runner/tests/e2e/001.visio.gz delete mode 100644 paperio/local_runner/tests/test_e2e.py diff --git a/paperio/local_runner/RewindClient.py b/paperio/local_runner/RewindClient.py deleted file mode 100644 index ba9d5e8..0000000 --- a/paperio/local_runner/RewindClient.py +++ /dev/null @@ -1,69 +0,0 @@ -import _socket -import json -from enum import Enum - -class RewindClient(): - def __init__(self, host=None, port=None): - self.socket = _socket.socket() - self.socket.setsockopt(_socket.IPPROTO_TCP, _socket.TCP_NODELAY, True) - if host is None: - host = "127.0.0.1" - port = 9111 - self.socket.connect((host, port)) - - def close(self): - self.socket.close() - - def _send(self, obj): - if self.socket: - self.socket.sendall(json.dumps(obj).encode('utf-8')) - - def circle(self, x, y, radius, color, layer=3): - self._send({ - 'type': 'circle', - 'x': x, - 'y': y, - 'r': radius, - 'color': color, - 'layer': layer - }) - - def rectangle(self, x1, y1, x2, y2, color, layer=3): - self._send({ - 'type': 'rectangle', - 'x1': x1, - 'y1': y1, - 'x2': x2, - 'y2': y2, - 'color': color, - 'layer': layer - }) - - def line(self, x1, y1, x2, y2, color, layer=3): - self._send({ - 'type': 'line', - 'x1': x1, - 'y1': y1, - 'x2': x2, - 'y2': y2, - 'color': color, - 'layer': layer - }) - - def message(self, msg): - self._send({ - 'type': 'message', - 'message': msg - }) - - def popup(self, x, y, r, text): - self._send({ - 'type': 'popup', - 'x': x, - 'y': y, - 'r': r, - 'text': text, - }) - - def end_frame(self): - self._send({'type': 'end'}) diff --git a/paperio/local_runner/clients.py b/paperio/local_runner/clients.py index 27e6622..1538a5b 100644 --- a/paperio/local_runner/clients.py +++ b/paperio/local_runner/clients.py @@ -7,7 +7,7 @@ from subprocess import Popen, PIPE import pyglet -from constants import LEFT, RIGHT, UP, DOWN, MAX_EXECUTION_TIME, REQUEST_MAX_TIME +from constants import CONSTS class Client(object): @@ -31,10 +31,10 @@ class KeyboardClient(Client): @property def KEY_COMMAND_MAP(self): return { - pyglet.window.key.MOTION_LEFT: LEFT, - pyglet.window.key.MOTION_RIGHT: RIGHT, - pyglet.window.key.MOTION_DOWN: DOWN, - pyglet.window.key.MOTION_UP: UP, + pyglet.window.key.MOTION_LEFT: CONSTS.LEFT, + pyglet.window.key.MOTION_RIGHT: CONSTS.RIGHT, + pyglet.window.key.MOTION_DOWN: CONSTS.DOWN, + pyglet.window.key.MOTION_UP: CONSTS.UP, } def __init__(self, window): @@ -55,10 +55,10 @@ class KeyboardClient2(KeyboardClient): @property def KEY_COMMAND_MAP(self): return { - pyglet.window.key.A: LEFT, - pyglet.window.key.D: RIGHT, - pyglet.window.key.S: DOWN, - pyglet.window.key.W: UP, + pyglet.window.key.A: CONSTS.LEFT, + pyglet.window.key.D: CONSTS.RIGHT, + pyglet.window.key.S: CONSTS.DOWN, + pyglet.window.key.W: CONSTS.UP, } def __init__(self, window): @@ -83,7 +83,7 @@ def __init__(self): self.position = None def change_command(self): - commands = [LEFT, DOWN, RIGHT, UP] + commands = [CONSTS.LEFT, CONSTS.DOWN, CONSTS.RIGHT, CONSTS.UP] command = commands[self.next_dir % 4] self.next_dir += 1 self.command = command @@ -91,16 +91,16 @@ def change_command(self): def get_next_point(self): x, y = self.position - if self.command == UP: + if self.command == CONSTS.UP: return x, y + self.width - if self.command == DOWN: + if self.command == CONSTS.DOWN: return x, y - self.width - if self.command == LEFT: + if self.command == CONSTS.LEFT: return x - self.width, y - if self.command == RIGHT: + if self.command == CONSTS.RIGHT: return x + self.width, y def is_border(self, point): @@ -148,7 +148,7 @@ def send_message(self, t, d): class TcpClient(Client): - EXECUTION_LIMIT = datetime.timedelta(seconds=MAX_EXECUTION_TIME) + EXECUTION_LIMIT = datetime.timedelta(seconds=CONSTS.MAX_EXECUTION_TIME) def __init__(self, reader, writer): self.reader = reader @@ -169,7 +169,7 @@ def save_log_to_disk(self, log, path): } async def set_solution_id(self): - hello_json = await asyncio.wait_for(self.reader.readline(), timeout=REQUEST_MAX_TIME) + hello_json = await asyncio.wait_for(self.reader.readline(), timeout=CONSTS.REQUEST_MAX_TIME) try: self.solution_id = json.loads(hello_json.decode('utf-8')).get('solution_id') except ValueError: @@ -180,8 +180,7 @@ async def set_solution_id(self): def send_message(self, t, d): msg = { 'type': t, - 'params': d, - 'time_left': round((self.EXECUTION_LIMIT-self.execution_time).total_seconds()*1000) + 'params': d } msg_bytes = '{}\n'.format(json.dumps(msg)).encode() self.writer.write(msg_bytes) @@ -189,7 +188,7 @@ def send_message(self, t, d): async def get_command(self): try: before = datetime.datetime.now() - z = await asyncio.wait_for(self.reader.readline(), timeout=REQUEST_MAX_TIME) + z = await asyncio.wait_for(self.reader.readline(), timeout=CONSTS.REQUEST_MAX_TIME) if not z: raise ConnectionError('Connection closed') self.execution_time += (datetime.datetime.now() - before) diff --git a/paperio/local_runner/constants.py b/paperio/local_runner/constants.py index cae8c51..3ea8192 100644 --- a/paperio/local_runner/constants.py +++ b/paperio/local_runner/constants.py @@ -16,40 +16,41 @@ def parse_json(value, default=None): return default -LEFT = 'left' -RIGHT = 'right' -UP = 'up' -DOWN = 'down' - -SPEED = toint(os.getenv('SPEED'), 5) -WIDTH = toint(os.getenv('WIDTH'), 30) # должно делиться на 2 -BONUS_CHANCE = toint(os.getenv('BONUS_CHANCE'), 500) # 1 из BONUS_CHANCE -BONUSES_MAX_COUNT = toint(os.getenv('BONUSES_MAX_COUNT'), 3) -Y_CELLS_COUNT = toint(os.getenv('Y_CELLS_COUNT'), 31) -X_CELLS_COUNT = toint(os.getenv('X_CELLS_COUNT'), 31) - -NEUTRAL_TERRITORY_SCORE = toint(os.getenv('NEUTRAL_TERRITORY_SCORE'), 1) -ENEMY_TERRITORY_SCORE = toint(os.getenv('ENEMY_TERRITORY_SCORE'), 5) -SAW_SCORE = toint(os.getenv('SAW_SCORE'), 30) -LINE_KILL_SCORE = toint(os.getenv('LINE_KILL_SCORE'), 50) -SAW_KILL_SCORE = toint(os.getenv('SAW_KILL_SCORE'), 150) - -LR_CLIENTS_MAX_COUNT = toint(os.getenv('LR_CLIENTS_MAX_COUNT'), 6) - -MAX_EXECUTION_TIME = toint(os.getenv('MAX_EXECUTION_TIME'), 120) -REQUEST_MAX_TIME = toint(os.getenv('REQUEST_MAX_TIME'), 5) -MAX_TICK_COUNT = toint(os.getenv('MAX_TICK_COUNT'), 1500) -CLIENTS_COUNT = toint(os.getenv('CLIENTS_COUNT'), 2) -AVAILABLE_BONUSES = parse_json(os.getenv('AVAILABLE_BONUSES'), ['n', 's', 'saw']) - -PLAYER_COLORS = [ - (90, 159, 153, 255), - (216, 27, 96, 255), - (96, 125, 139, 255), - (245, 124, 0, 255), - (92, 107, 192, 255), - (141, 110, 99, 255) -] - -WINDOW_HEIGHT = Y_CELLS_COUNT * WIDTH -WINDOW_WIDTH = X_CELLS_COUNT * WIDTH +class CONSTS: + LEFT = 'left' + RIGHT = 'right' + UP = 'up' + DOWN = 'down' + + SPEED = toint(os.getenv('SPEED'), 5) + WIDTH = toint(os.getenv('WIDTH'), 30) # должно делиться на 2 + BONUS_CHANCE = toint(os.getenv('BONUS_CHANCE'), 500) # 1 из BONUS_CHANCE + BONUSES_MAX_COUNT = toint(os.getenv('BONUSES_MAX_COUNT'), 3) + Y_CELLS_COUNT = toint(os.getenv('Y_CELLS_COUNT'), 31) + X_CELLS_COUNT = toint(os.getenv('X_CELLS_COUNT'), 31) + + NEUTRAL_TERRITORY_SCORE = toint(os.getenv('NEUTRAL_TERRITORY_SCORE'), 1) + ENEMY_TERRITORY_SCORE = toint(os.getenv('ENEMY_TERRITORY_SCORE'), 5) + SAW_SCORE = toint(os.getenv('SAW_SCORE'), 30) + LINE_KILL_SCORE = toint(os.getenv('LINE_KILL_SCORE'), 50) + SAW_KILL_SCORE = toint(os.getenv('SAW_KILL_SCORE'), 150) + + LR_CLIENTS_MAX_COUNT = toint(os.getenv('LR_CLIENTS_MAX_COUNT'), 6) + + MAX_EXECUTION_TIME = toint(os.getenv('MAX_EXECUTION_TIME'), 120) + REQUEST_MAX_TIME = toint(os.getenv('REQUEST_MAX_TIME'), 5) + MAX_TICK_COUNT = toint(os.getenv('MAX_TICK_COUNT'), 1500) + CLIENTS_COUNT = toint(os.getenv('CLIENTS_COUNT'), 2) + AVAILABLE_BONUSES = parse_json(os.getenv('AVAILABLE_BONUSES'), ['n', 's', 'saw']) + + PLAYER_COLORS = [ + (90, 159, 153, 255), + (216, 27, 96, 255), + (96, 125, 139, 255), + (245, 124, 0, 255), + (92, 107, 192, 255), + (141, 110, 99, 255) + ] + + WINDOW_HEIGHT = Y_CELLS_COUNT * WIDTH + WINDOW_WIDTH = X_CELLS_COUNT * WIDTH diff --git a/paperio/local_runner/game_objects/bonuses.py b/paperio/local_runner/game_objects/bonuses.py index a70a508..ed0fe31 100644 --- a/paperio/local_runner/game_objects/bonuses.py +++ b/paperio/local_runner/game_objects/bonuses.py @@ -1,7 +1,7 @@ import random from helpers import draw_square_with_image, get_random_coordinates, batch_draw -from constants import WIDTH +from constants import CONSTS class Bonus: @@ -24,7 +24,7 @@ def generate_active_ticks(): @staticmethod def is_available_point(x, y, players, busy_points): for p in players: - if (p.x - 2 * WIDTH <= x <= p.x + 2 * WIDTH) and (p.y - 2 * WIDTH <= y <= p.y + 2 * WIDTH): + if (p.x - 2 * CONSTS.WIDTH <= x <= p.x + 2 * CONSTS.WIDTH) and (p.y - 2 * CONSTS.WIDTH <= y <= p.y + 2 * CONSTS.WIDTH): return False return (x, y) not in busy_points @@ -64,15 +64,15 @@ def apply(self, player): else: player.bonuses.append(self) - while player.speed < WIDTH: + while player.speed < CONSTS.WIDTH: player.speed += 1 - if WIDTH % player.speed == 0: + if CONSTS.WIDTH % player.speed == 0: break def cancel(self, player): while player.speed > 1: player.speed -= 1 - if WIDTH % player.speed == 0: + if CONSTS.WIDTH % player.speed == 0: break @@ -91,13 +91,13 @@ def apply(self, player): while player.speed > 1: player.speed -= 1 - if WIDTH % player.speed == 0: + if CONSTS.WIDTH % player.speed == 0: break def cancel(self, player): - while player.speed < WIDTH: + while player.speed < CONSTS.WIDTH: player.speed += 1 - if WIDTH % player.speed == 0: + if CONSTS.WIDTH % player.speed == 0: break diff --git a/paperio/local_runner/game_objects/game.py b/paperio/local_runner/game_objects/game.py index 0144c7f..57d1286 100644 --- a/paperio/local_runner/game_objects/game.py +++ b/paperio/local_runner/game_objects/game.py @@ -6,17 +6,13 @@ import random from helpers import is_intersect -from constants import WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH, PLAYER_COLORS, MAX_TICK_COUNT, BONUS_CHANCE, \ - BONUSES_MAX_COUNT, X_CELLS_COUNT, Y_CELLS_COUNT, SPEED, NEUTRAL_TERRITORY_SCORE, ENEMY_TERRITORY_SCORE, \ - LINE_KILL_SCORE, SAW_KILL_SCORE, AVAILABLE_BONUSES, SAW_SCORE +from constants import CONSTS from game_objects.player import Player from game_objects.bonuses import Nitro, Slowdown, Bonus, Saw class Game: - available_bonuses = [b for b in [Nitro, Slowdown, Saw] if b.visio_name in AVAILABLE_BONUSES] - - RESULT_LOCATION = os.environ.get('GAME_LOG_LOCATION', './result') + RESULT_LOCATION = os.environ.get('GAME_LOG_LOCATION', './results/result') BASE_DIR = os.path.dirname(RESULT_LOCATION) @@ -35,14 +31,14 @@ def get_busy_points(self): def generate_bonus(self): if len(self.available_bonuses) > 0: - if random.randint(1, BONUS_CHANCE) == 1 and len(self.bonuses) < BONUSES_MAX_COUNT: + if random.randint(1, CONSTS.BONUS_CHANCE) == 1 and len(self.bonuses) < CONSTS.BONUSES_MAX_COUNT: coors = Bonus.generate_coordinates(self.players, self.get_busy_points()) bonus = random.choice(self.available_bonuses)(coors) self.bonuses.append(bonus) def get_coordinates(self, clients_count): - dx = round(X_CELLS_COUNT / 6) * WIDTH - dy = round(Y_CELLS_COUNT / 6) * WIDTH + dx = round(CONSTS.X_CELLS_COUNT / 6) * CONSTS.WIDTH + dy = round(CONSTS.Y_CELLS_COUNT / 6) * CONSTS.WIDTH if clients_count == 1: coors = [(3 * dx, 3 * dy)] @@ -59,30 +55,30 @@ def get_coordinates(self, clients_count): (4 * dx, 4 * dy), ] else: - x = round(X_CELLS_COUNT / 5) * WIDTH - y = (WINDOW_HEIGHT + WINDOW_WIDTH - 4 * x) / 3 - b = (WINDOW_WIDTH - 2 * x) / 2 + x = round(CONSTS.X_CELLS_COUNT / 5) * CONSTS.WIDTH + y = (CONSTS.WINDOW_HEIGHT + CONSTS.WINDOW_WIDTH - 4 * x) / 3 + b = (CONSTS.WINDOW_WIDTH - 2 * x) / 2 a = y - b coors = [ (x, x + a), - (x, x + a + y + WIDTH), + (x, x + a + y + CONSTS.WIDTH), - (round(WINDOW_WIDTH / 2), WINDOW_HEIGHT - x + WIDTH), - (round(WINDOW_WIDTH / 2), x), + (round(CONSTS.WINDOW_WIDTH / 2), CONSTS.WINDOW_HEIGHT - x + CONSTS.WIDTH), + (round(CONSTS.WINDOW_WIDTH / 2), x), - (WINDOW_WIDTH - x + WIDTH, x + a), - (WINDOW_WIDTH - x + WIDTH, x + a + y + WIDTH), + (CONSTS.WINDOW_WIDTH - x + CONSTS.WIDTH, x + a), + (CONSTS.WINDOW_WIDTH - x + CONSTS.WIDTH, x + a + y + CONSTS.WIDTH), ] - coors = [(round(x / WIDTH) * WIDTH - round(WIDTH / 2), round(y / WIDTH) * WIDTH - round(WIDTH / 2)) for x, y in coors] + coors = [(round(x / CONSTS.WIDTH) * CONSTS.WIDTH - round(CONSTS.WIDTH / 2), round(y / CONSTS.WIDTH) * CONSTS.WIDTH - round(CONSTS.WIDTH / 2)) for x, y in coors] yield from coors def __init__(self, clients): players = [] coordinates = self.get_coordinates(len(clients)) for index, client in enumerate(clients): - players.append(Player(index + 1, *next(coordinates), 'Player {}'.format(index + 1), PLAYER_COLORS[index], client)) + players.append(Player(index + 1, *next(coordinates), 'Player {}'.format(index + 1), CONSTS.PLAYER_COLORS[index], client)) self.players = players self.losers = [] @@ -90,6 +86,7 @@ def __init__(self, clients): self.game_log = [] self.events = [] self.tick = 1 + self.available_bonuses = [b for b in [Nitro, Slowdown, Saw] if b.visio_name in CONSTS.AVAILABLE_BONUSES] def append_event(self, event, p1, p2=None): row = { @@ -104,36 +101,34 @@ def append_event(self, event, p1, p2=None): def check_loss(self, player, players): is_loss = False - if player.y < 0 + round(WIDTH / 2): + if player.y < 0 + round(CONSTS.WIDTH / 2): is_loss = True self.append_event('faced the border', player) - if player.y > WINDOW_HEIGHT - round(WIDTH / 2): + if player.y > CONSTS.WINDOW_HEIGHT - round(CONSTS.WIDTH / 2): is_loss = True self.append_event('faced the border', player) - if player.x < 0 + round(WIDTH / 2): + if player.x < 0 + round(CONSTS.WIDTH / 2): is_loss = True self.append_event('faced the border', player) - if player.x > WINDOW_WIDTH - round(WIDTH / 2): + if player.x > CONSTS.WINDOW_WIDTH - round(CONSTS.WIDTH / 2): is_loss = True self.append_event('faced the border', player) for p in players: if (p.x, p.y) in player.lines[:-1]: if p != player: - p.tick_score += LINE_KILL_SCORE + p.tick_score += CONSTS.LINE_KILL_SCORE is_loss = True self.append_event('line crossed by other player', player, p) - if len(player.lines) > 0: - for p in players: - if is_intersect((player.x, player.y), (p.x, p.y)) and p != player: - if len(player.lines) >= len(p.lines): - is_loss = True - self.append_event('faced with other player', player, p) - break + for p in players: + if is_intersect((player.x, player.y), (p.x, p.y)) and p != player: + if len(player.lines) >= len(p.lines): + is_loss = True + self.append_event('faced with other player', player, p) if len(player.territory.points) == 0: is_loss = True @@ -143,10 +138,10 @@ def check_loss(self, player, players): def send_game_start(self): start_message = { - 'x_cells_count': X_CELLS_COUNT, - 'y_cells_count': Y_CELLS_COUNT, - 'speed': SPEED, - 'width': WIDTH + 'x_cells_count': CONSTS.X_CELLS_COUNT, + 'y_cells_count': CONSTS.Y_CELLS_COUNT, + 'speed': CONSTS.SPEED, + 'width': CONSTS.WIDTH } self.game_log.append({'type': 'start_game', **start_message}) for player in self.players: @@ -161,7 +156,7 @@ def send_game_end(self): for player in self.players: player.send_message('end_game', {}) - def append_tick_to_game_log(self): + def send_game_tick(self): self.game_log.append({ 'type': 'tick', 'players': self.get_players_states(), @@ -170,22 +165,21 @@ def append_tick_to_game_log(self): 'saw': Saw.log }) - Saw.log = [] - - def send_game_tick(self): for player in self.players: - if (player.x - round(WIDTH / 2)) % WIDTH == 0 and (player.y - round(WIDTH / 2)) % WIDTH == 0: + if (player.x - round(CONSTS.WIDTH / 2)) % CONSTS.WIDTH == 0 and (player.y - round(CONSTS.WIDTH / 2)) % CONSTS.WIDTH == 0: player.send_message('tick', { 'players': self.get_players_states(player), 'bonuses': self.get_bonuses_states(), 'tick_num': self.tick, }) + Saw.log = [] + async def game_loop_wrapper(self, *args, **kwargs): self.send_game_start() while True: is_game_over = await self.game_loop(*args, **kwargs) - if is_game_over or self.tick >= MAX_TICK_COUNT: + if is_game_over or self.tick >= CONSTS.MAX_TICK_COUNT: self.send_game_end() self.game_save() break @@ -217,16 +211,16 @@ async def get_command_wrapper(self, player): async def game_loop(self, *args, **kwargs): self.send_game_tick() + # if self.tick == 2: + # time.sleep(0) futures = [] for player in self.players: - if (player.x - round(WIDTH / 2)) % WIDTH == 0 and (player.y - round(WIDTH / 2)) % WIDTH == 0: + if (player.x - round(CONSTS.WIDTH / 2)) % CONSTS.WIDTH == 0 and (player.y - round(CONSTS.WIDTH / 2)) % CONSTS.WIDTH == 0: futures.append(asyncio.ensure_future(self.get_command_wrapper(player))) if futures: await asyncio.wait(futures) - self.append_tick_to_game_log() - for player in self.players: player.move() @@ -234,14 +228,14 @@ async def game_loop(self, *args, **kwargs): for player in self.players: player.remove_saw_bonus() - if (player.x - round(WIDTH / 2)) % WIDTH == 0 and (player.y - round(WIDTH / 2)) % WIDTH == 0: + if (player.x - round(CONSTS.WIDTH / 2)) % CONSTS.WIDTH == 0 and (player.y - round(CONSTS.WIDTH / 2)) % CONSTS.WIDTH == 0: player.update_lines() captured = player.territory.capture(player.lines) players_to_captured[player] = captured if len(captured) > 0: player.lines.clear() - player.tick_score += NEUTRAL_TERRITORY_SCORE * len(captured) + player.tick_score += CONSTS.NEUTRAL_TERRITORY_SCORE * len(captured) for player in self.players: is_loss = self.check_loss(player, self.players) @@ -257,7 +251,7 @@ async def game_loop(self, *args, **kwargs): self.losers.append(player) for player in self.players: - if (player.x - round(WIDTH / 2)) % WIDTH == 0 and (player.y - round(WIDTH / 2)) % WIDTH == 0: + if (player.x - round(CONSTS.WIDTH / 2)) % CONSTS.WIDTH == 0 and (player.y - round(CONSTS.WIDTH / 2)) % CONSTS.WIDTH == 0: captured = players_to_captured.get(player, set()) player.tick_action() @@ -280,11 +274,11 @@ async def game_loop(self, *args, **kwargs): 'loser': p.id, 'killed': True }) - player.tick_score += SAW_KILL_SCORE + player.tick_score += CONSTS.SAW_KILL_SCORE else: removed = p.territory.split(line, player.direction, p) if len(removed) > 0: - player.tick_score += SAW_SCORE + player.tick_score += CONSTS.SAW_SCORE Saw.append_territory(removed, p.territory.color) Saw.log.append({ 'player': player.id, @@ -297,7 +291,7 @@ async def game_loop(self, *args, **kwargs): for p in self.players: if p != player: removed = p.territory.remove_points(captured) - player.tick_score += (ENEMY_TERRITORY_SCORE - NEUTRAL_TERRITORY_SCORE) * len(removed) + player.tick_score += (CONSTS.ENEMY_TERRITORY_SCORE - CONSTS.NEUTRAL_TERRITORY_SCORE) * len(removed) for player in self.losers: if player in self.players: @@ -410,7 +404,7 @@ def draw(self): if len(self.players) == 0: self.scene.show_game_over() - elif self.timeout and self.tick >= MAX_TICK_COUNT: + elif self.timeout and self.tick >= CONSTS.MAX_TICK_COUNT: self.scene.show_game_over(timeout=True) async def game_loop(self, *args, **kwargs): diff --git a/paperio/local_runner/game_objects/player.py b/paperio/local_runner/game_objects/player.py index 7c518a0..464df18 100644 --- a/paperio/local_runner/game_objects/player.py +++ b/paperio/local_runner/game_objects/player.py @@ -1,12 +1,11 @@ from copy import copy from game_objects.territory import Territory from game_objects.bonuses import Saw -from constants import UP, DOWN, LEFT, RIGHT, SPEED, WINDOW_HEIGHT, WINDOW_WIDTH, WIDTH +from constants import CONSTS from helpers import batch_draw, draw_square class Player: - speed = SPEED direction = None def __init__(self, id, x, y, name, color, client): @@ -25,31 +24,32 @@ def __init__(self, id, x, y, name, color, client): self.debug_log = [] self.client = client self.is_disconnected = False + self.speed = CONSTS.SPEED def change_direction(self, command): - if command == UP and self.direction != DOWN: - self.direction = UP + if command == CONSTS.UP and self.direction != CONSTS.DOWN: + self.direction = CONSTS.UP - if command == DOWN and self.direction != UP: - self.direction = DOWN + if command == CONSTS.DOWN and self.direction != CONSTS.UP: + self.direction = CONSTS.DOWN - if command == LEFT and self.direction != RIGHT: - self.direction = LEFT + if command == CONSTS.LEFT and self.direction != CONSTS.RIGHT: + self.direction = CONSTS.LEFT - if command == RIGHT and self.direction != LEFT: - self.direction = RIGHT + if command == CONSTS.RIGHT and self.direction != CONSTS.LEFT: + self.direction = CONSTS.RIGHT def move(self): - if self.direction == UP: + if self.direction == CONSTS.UP: self.y += self.speed - if self.direction == DOWN: + if self.direction == CONSTS.DOWN: self.y -= self.speed - if self.direction == LEFT: + if self.direction == CONSTS.LEFT: self.x -= self.speed - if self.direction == RIGHT: + if self.direction == CONSTS.RIGHT: self.x += self.speed def draw_lines(self): @@ -93,7 +93,7 @@ def get_state(self): return { 'score': self.score, 'direction': self.direction, - 'territory': list(sorted(self.territory.points)), + 'territory': list(self.territory.points), 'lines': copy(self.lines), 'position': (self.x, self.y), 'bonuses': self.get_bonuses_state() @@ -145,7 +145,7 @@ def save_log(self, path): def _get_line(self, dx, dy): x, y = self.x, self.y points = [] - while 0 < x < WINDOW_WIDTH and 0 < y < WINDOW_HEIGHT: + while 0 < x < CONSTS.WINDOW_WIDTH and 0 < y < CONSTS.WINDOW_HEIGHT: x += dx y += dy points.append((x, y)) @@ -153,29 +153,29 @@ def _get_line(self, dx, dy): return points def get_direction_line(self): - if self.direction == UP: - return self._get_line(0, WIDTH) + if self.direction == CONSTS.UP: + return self._get_line(0, CONSTS.WIDTH) - if self.direction == DOWN: - return self._get_line(0, -WIDTH) + if self.direction == CONSTS.DOWN: + return self._get_line(0, -CONSTS.WIDTH) - if self.direction == LEFT: - return self._get_line(-WIDTH, 0) + if self.direction == CONSTS.LEFT: + return self._get_line(-CONSTS.WIDTH, 0) - if self.direction == RIGHT: - return self._get_line(WIDTH, 0) + if self.direction == CONSTS.RIGHT: + return self._get_line(CONSTS.WIDTH, 0) def diff_position(self, direction, x, y, val): - if direction == UP: + if direction == CONSTS.UP: return x, y - val - if direction == DOWN: + if direction == CONSTS.DOWN: return x, y + val - if direction == LEFT: + if direction == CONSTS.LEFT: return x + val, y - if direction == RIGHT: + if direction == CONSTS.RIGHT: return x - val, y def get_position(self): @@ -183,7 +183,7 @@ def get_position(self): return self.x, self.y x, y = self.x, self.y - while not ((x - round(WIDTH / 2)) % WIDTH == 0 and (y - round(WIDTH / 2)) % WIDTH == 0): + while not ((x - round(CONSTS.WIDTH / 2)) % CONSTS.WIDTH == 0 and (y - round(CONSTS.WIDTH / 2)) % CONSTS.WIDTH == 0): x, y = self.diff_position(self.direction, x, y, self.speed) return (x, y), (x, y) != (self.x, self.y) @@ -191,7 +191,7 @@ def get_position(self): def get_prev_position(self): if self.direction is None: return self.x, self.y - return self.diff_position(self.direction, self.x, self.y, WIDTH) + return self.diff_position(self.direction, self.x, self.y, CONSTS.WIDTH) def is_ate(self, players_to_captured): for p, captured in players_to_captured.items(): diff --git a/paperio/local_runner/game_objects/scene.py b/paperio/local_runner/game_objects/scene.py index a5a1c7a..ef6b04d 100644 --- a/paperio/local_runner/game_objects/scene.py +++ b/paperio/local_runner/game_objects/scene.py @@ -1,6 +1,6 @@ import pyglet -from constants import WINDOW_HEIGHT, WINDOW_WIDTH, WIDTH +from constants import CONSTS from helpers import draw_quadrilateral, draw_line @@ -8,19 +8,19 @@ class Grid: def __init__(self, color): self.batch = pyglet.graphics.Batch() - y = WIDTH - while (y < WINDOW_HEIGHT): + y = CONSTS.WIDTH + while (y < CONSTS.WINDOW_HEIGHT): self.batch.add(2, pyglet.gl.GL_LINES, None, - ('v2i', (0, y, WINDOW_WIDTH, y)), + ('v2i', (0, y, CONSTS.WINDOW_WIDTH, y)), ('c4B', 2 * color)) - y += WIDTH + y += CONSTS.WIDTH - x = WIDTH - while (x < WINDOW_WIDTH): + x = CONSTS.WIDTH + while (x < CONSTS.WINDOW_WIDTH): self.batch.add(2, pyglet.gl.GL_LINES, None, - ('v2i', (x, 0, x, WINDOW_HEIGHT)), + ('v2i', (x, 0, x, CONSTS.WINDOW_HEIGHT)), ('c4B', 2 * color)) - x += WIDTH + x += CONSTS.WIDTH def draw(self): self.batch.draw() @@ -39,21 +39,22 @@ class Scene: leaderboard_rows_count = 0 labels_buffer = [] - game_over_label = pyglet.text.Label('GAME OVER', font_name='Times New Roman', - font_size=30, - color=game_over_label_color, - x=WINDOW_WIDTH / 2, y=WINDOW_HEIGHT / 2, - anchor_x='center', anchor_y='center') def __init__(self, scale): - self.window = pyglet.window.Window(height=int(WINDOW_HEIGHT * scale / 100), - width=int(WINDOW_WIDTH * scale / 100), + self.window = pyglet.window.Window(height=int(CONSTS.WINDOW_HEIGHT * scale / 100), + width=int(CONSTS.WINDOW_WIDTH * scale / 100), resizable=True) pyglet.options['debug_gl'] = False pyglet.gl.glClearColor(*self.background_color) pyglet.gl.glEnable(pyglet.gl.GL_BLEND) pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA, pyglet.gl.GL_ONE_MINUS_SRC_ALPHA) + self.grid = Grid(self.grid_color) + self.game_over_label = pyglet.text.Label('GAME OVER', font_name='Times New Roman', + font_size=30, + color=self.game_over_label_color, + x=CONSTS.WINDOW_WIDTH / 2, y=CONSTS.WINDOW_HEIGHT / 2, + anchor_x='center', anchor_y='center') def clear(self): self.window.clear() @@ -69,8 +70,8 @@ def append_label_to_leaderboard(self, label, color): font_name='Times New Roman', font_size=16, color=color, - x=WINDOW_WIDTH - self.leaderboard_width + 20, - y=WINDOW_HEIGHT - 20 - WIDTH / 2 - 30 * self.leaderboard_rows_count, + x=CONSTS.WINDOW_WIDTH - self.leaderboard_width + 20, + y=CONSTS.WINDOW_HEIGHT - 20 - CONSTS.WIDTH / 2 - 30 * self.leaderboard_rows_count, anchor_x='left', anchor_y='center') ) self.leaderboard_rows_count += 1 @@ -86,16 +87,16 @@ def draw_grid(self): self.grid.draw() def draw_border(self): - draw_line((0, 0), (0, WINDOW_HEIGHT), self.border_color, self.border_width) - draw_line((0, WINDOW_HEIGHT), (WINDOW_WIDTH, WINDOW_HEIGHT), self.border_color, self.border_width) - draw_line((WINDOW_WIDTH, WINDOW_HEIGHT), (WINDOW_WIDTH, 0), self.border_color, self.border_width) - draw_line((WINDOW_WIDTH, 0), (0, 0), self.border_color, self.border_width) + draw_line((0, 0), (0, CONSTS.WINDOW_HEIGHT), self.border_color, self.border_width) + draw_line((0, CONSTS.WINDOW_HEIGHT), (CONSTS.WINDOW_WIDTH, CONSTS.WINDOW_HEIGHT), self.border_color, self.border_width) + draw_line((CONSTS.WINDOW_WIDTH, CONSTS.WINDOW_HEIGHT), (CONSTS.WINDOW_WIDTH, 0), self.border_color, self.border_width) + draw_line((CONSTS.WINDOW_WIDTH, 0), (0, 0), self.border_color, self.border_width) def draw_leaderboard(self): - draw_quadrilateral((WINDOW_WIDTH - self.leaderboard_width, WINDOW_HEIGHT - self.leaderboard_height, - WINDOW_WIDTH, WINDOW_HEIGHT - self.leaderboard_height, - WINDOW_WIDTH, WINDOW_HEIGHT, - WINDOW_WIDTH - self.leaderboard_width, WINDOW_HEIGHT), + draw_quadrilateral((CONSTS.WINDOW_WIDTH - self.leaderboard_width, CONSTS.WINDOW_HEIGHT - self.leaderboard_height, + CONSTS.WINDOW_WIDTH, CONSTS.WINDOW_HEIGHT - self.leaderboard_height, + CONSTS.WINDOW_WIDTH, CONSTS.WINDOW_HEIGHT, + CONSTS.WINDOW_WIDTH - self.leaderboard_width, CONSTS.WINDOW_HEIGHT), self.leaderboard_color) for label in self.labels_buffer[:self.leaderboard_rows_count]: label.draw() diff --git a/paperio/local_runner/game_objects/territory.py b/paperio/local_runner/game_objects/territory.py index 21cb1f8..a15cf50 100644 --- a/paperio/local_runner/game_objects/territory.py +++ b/paperio/local_runner/game_objects/territory.py @@ -1,5 +1,5 @@ from helpers import in_polygon, batch_draw_territory, get_neighboring, get_vert_and_horiz -from constants import WIDTH, LEFT, RIGHT, UP, DOWN +from constants import CONSTS import networkx as nx @@ -7,6 +7,8 @@ class Territory: def __init__(self, x, y, color): self.color = color self.points = {(x, y), *get_neighboring((x, y))} + # fro scripted_coll_8 + # self.points = {(x, y), *get_neighboring((x, y)), (x - WIDTH, y - 3 * WIDTH), (x, y - 4 * WIDTH), (x + WIDTH, y - 3 * WIDTH)} self.changed = True def draw(self): @@ -20,10 +22,12 @@ def get_boundary(self): boundary.append(point) return boundary - def get_nearest_boundary(self, point, boundary): + def _get_start_points(self, point, boundary): + res = [] for neighbor in [point, *get_neighboring(point)]: if neighbor in boundary: - return neighbor + res.append(neighbor) + return res def _capture(self, boundary): poligon_x_arr = [x for x, _ in boundary] @@ -41,8 +45,8 @@ def _capture(self, boundary): while y > min_y: if (x, y) not in self.points and in_polygon(x, y, poligon_x_arr, poligon_y_arr): captured.append((x, y)) - y -= WIDTH - x -= WIDTH + y -= CONSTS.WIDTH + x -= CONSTS.WIDTH return captured def is_siblings(self, p1, p2): @@ -50,14 +54,15 @@ def is_siblings(self, p1, p2): def get_voids_between_lines_and_territory(self, lines): boundary = self.get_boundary() + graph = self.get_graph(boundary) voids = [] for i_lp1, lp1 in enumerate(lines): for point in get_neighboring(lp1): if point in boundary: prev = None for lp2 in lines[:i_lp1 + 1]: - start_point = self.get_nearest_boundary(lp2, boundary) - if start_point: + start_points = self._get_start_points(lp2, boundary) + for start_point in start_points: if prev and (self.is_siblings(prev, start_point) or prev == start_point): prev = start_point continue @@ -65,7 +70,7 @@ def get_voids_between_lines_and_territory(self, lines): start_index = boundary.index(start_point) try: - path = self.get_path(start_index, end_index, boundary) + path = nx.shortest_path(graph, end_index, start_index, weight='weight') except (nx.NetworkXNoPath, nx.NodeNotFound): continue @@ -76,7 +81,7 @@ def get_voids_between_lines_and_territory(self, lines): lines_path = lines[lines.index(lp2):i_lp1 + 1] voids.append(lines_path + path) - prev = start_point + prev = start_point return voids def capture_voids_between_lines(self, lines): @@ -122,14 +127,13 @@ def remove_points(self, points): def get_siblings(self, point, boundary): return [sibling for sibling in get_neighboring(point) if sibling in boundary] - def get_path(self, start_index, end_index, boundary): + def get_graph(self, boundary): graph = nx.Graph() for index, point in enumerate(boundary): siblings = self.get_siblings(point, boundary) for sibling in siblings: graph.add_edge(index, boundary.index(sibling), weight=1) - - return nx.shortest_path(graph, end_index, start_index, weight='weight') + return graph def split(self, line, direction, player): removed = [] @@ -137,7 +141,7 @@ def split(self, line, direction, player): if any([point in self.points for point in line]): for point in list(self.points): - if direction in [UP, DOWN]: + if direction in [CONSTS.UP, CONSTS.DOWN]: if player.x < l_point[0]: if point[0] >= l_point[0]: removed.append(point) @@ -147,7 +151,7 @@ def split(self, line, direction, player): removed.append(point) self.points.discard(point) - if direction in [LEFT, RIGHT]: + if direction in [CONSTS.LEFT, CONSTS.RIGHT]: if player.y < l_point[1]: if point[1] >= l_point[1]: removed.append(point) diff --git a/paperio/local_runner/helpers.py b/paperio/local_runner/helpers.py index ad3008b..43348ec 100644 --- a/paperio/local_runner/helpers.py +++ b/paperio/local_runner/helpers.py @@ -1,7 +1,7 @@ import os import random import pyglet -from constants import WIDTH, X_CELLS_COUNT, Y_CELLS_COUNT +from constants import CONSTS def show_coordinates(point): @@ -13,7 +13,8 @@ def show_coordinates(point): anchor_x='center', anchor_y='center').draw() -def get_square_coordinates(point, width=WIDTH): +def get_square_coordinates(point): + width = round(CONSTS.WIDTH / 2) x, y = point return (x - width, y - width, x + width, y - width, @@ -21,7 +22,8 @@ def get_square_coordinates(point, width=WIDTH): x - width, y + width) -def get_diagonals(point, width=WIDTH): +def get_diagonals(point): + width = CONSTS.WIDTH x, y = point return [ @@ -32,7 +34,8 @@ def get_diagonals(point, width=WIDTH): ] -def get_vert_and_horiz(point, width=WIDTH): +def get_vert_and_horiz(point): + width = CONSTS.WIDTH x, y = point return [ @@ -43,10 +46,10 @@ def get_vert_and_horiz(point, width=WIDTH): ] -def get_neighboring(point, width=WIDTH): +def get_neighboring(point): return [ - *get_vert_and_horiz(point, width), - *get_diagonals(point, width) + *get_vert_and_horiz(point), + *get_diagonals(point) ] @@ -57,20 +60,21 @@ def get_territory_line(point, points): while p in points: line_points.append(p) x, y = p - p = (x - WIDTH, y) - start = (p[0] + WIDTH, p[1]) + p = (x - CONSTS.WIDTH, y) + start = (p[0] + CONSTS.WIDTH, p[1]) p = point while p in points: line_points.append(p) x, y = p - p = (x + WIDTH, y) - end = (p[0] - WIDTH, p[1]) + p = (x + CONSTS.WIDTH, y) + end = (p[0] - CONSTS.WIDTH, p[1]) return line_points, start, end -def get_line_coordinates(start, end, width=WIDTH): +def get_line_coordinates(start, end): + width = CONSTS.WIDTH width = round(width / 2) x1, y1 = start x2, y2 = end @@ -85,9 +89,9 @@ def get_line_coordinates(start, end, width=WIDTH): TERRITORY_CACHE = {} -def batch_draw_territory(points, color, redraw, width=WIDTH): +def batch_draw_territory(points, color, redraw): if len(points) < 100: - batch_draw(points, color, width) + batch_draw(points, color) return if color not in TERRITORY_CACHE or redraw: @@ -97,7 +101,7 @@ def batch_draw_territory(points, color, redraw, width=WIDTH): if point not in excluded: line_points, start, end = get_territory_line(point, points) excluded.update(line_points) - coors = get_line_coordinates(start, end, width) + coors = get_line_coordinates(start, end) lines.append(coors) TERRITORY_CACHE[color] = [len(points), lines] else: @@ -111,12 +115,11 @@ def batch_draw_territory(points, color, redraw, width=WIDTH): pyglet.gl.glEnd() -def batch_draw(points, color, width=WIDTH): - width = round(width / 2) +def batch_draw(points, color): pyglet.gl.glColor4f(*[i/255 for i in color]) pyglet.gl.glBegin(pyglet.gl.GL_QUADS) for point in points: - square = get_square_coordinates(point, width) + square = get_square_coordinates(point) pyglet.graphics.glVertex2i(square[0], square[1]) pyglet.graphics.glVertex2i(square[2], square[3]) pyglet.graphics.glVertex2i(square[4], square[5]) @@ -124,9 +127,8 @@ def batch_draw(points, color, width=WIDTH): pyglet.gl.glEnd() -def draw_square(point, color, width=WIDTH): - width = round(width / 2) - coordinates = get_square_coordinates(point, width) +def draw_square(point, color): + coordinates = get_square_coordinates(point) pyglet.graphics.draw(4, pyglet.gl.GL_QUADS, ('v2i', coordinates), ('c4B', 4 * color)) @@ -134,7 +136,9 @@ def draw_quadrilateral(coordinates, color): pyglet.graphics.draw(4, pyglet.gl.GL_QUADS, ('v2i', coordinates), ('c4B', 4 * color)) -def draw_line(point1, point2, color, width=WIDTH): +def draw_line(point1, point2, color, width=None): + if not width: + width = CONSTS.WIDTH x1, y1 = point1 x2, y2 = point2 @@ -177,8 +181,9 @@ def load_image(path): return IMAGE_CACHE[path] -def draw_square_with_image(point, color, image_path, width=WIDTH): - draw_square(point, color, width) +def draw_square_with_image(point, color, image_path): + width = CONSTS.WIDTH + draw_square(point, color) x, y = point @@ -189,12 +194,13 @@ def draw_square_with_image(point, color, image_path, width=WIDTH): def get_random_coordinates(): - x = random.randint(1, X_CELLS_COUNT) * WIDTH - round(WIDTH / 2) - y = random.randint(1, Y_CELLS_COUNT) * WIDTH - round(WIDTH / 2) + x = random.randint(1, CONSTS.X_CELLS_COUNT) * CONSTS.WIDTH - round(CONSTS.WIDTH / 2) + y = random.randint(1, CONSTS.Y_CELLS_COUNT) * CONSTS.WIDTH - round(CONSTS.WIDTH / 2) return x, y -def is_intersect(p1, p2, width=WIDTH): +def is_intersect(p1, p2): + width = CONSTS.WIDTH x1, y1 = p1 x2, y2 = p2 return abs(x1 - x2) < width and abs(y1 - y2) < width diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py old mode 100755 new mode 100644 index 49ab49c..d85d17e --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -4,15 +4,17 @@ import os import gzip import json -import sys import pyglet +from pyglet.gl import * +from pyglet.window import key from helpers import TERRITORY_CACHE, load_image from clients import Client, KeyboardClient, SimplePythonClient, FileClient -from constants import LR_CLIENTS_MAX_COUNT, MAX_TICK_COUNT, WINDOW_WIDTH, WINDOW_HEIGHT, WIDTH +from constants import CONSTS from game_objects.game import LocalGame, Game from game_objects.bonuses import Bonus +from game_objects.scene import Scene loop = events.new_event_loop() @@ -20,7 +22,7 @@ parser = argparse.ArgumentParser(description='LocalRunner for paperio') -for i in range(1, LR_CLIENTS_MAX_COUNT + 1): +for i in range(1, CONSTS.LR_CLIENTS_MAX_COUNT + 1): parser.add_argument('-p{}'.format(i), '--player{}'.format(i), type=str, nargs='?', help='Path to executable with strategy for player {}'.format(i)) parser.add_argument('--p{}l'.format(i), type=str, nargs='?', help='Path to log for player {}'.format(i)) @@ -28,84 +30,21 @@ parser.add_argument('-t', '--timeout', type=str, nargs='?', help='off/on timeout', default='on') parser.add_argument('-s', '--scale', type=int, nargs='?', help='window scale (%%)', default=100) parser.add_argument('--replay', help='Replay visio.gz') -parser.add_argument('--no-gui', help='Disable default gui', action='store_true') -parser.add_argument('-r', '--rewind-viewer', help='RewindViewer', action='store_true') args = parser.parse_args() -if not args.no_gui: - from pyglet.gl import * - from pyglet.window import key - from game_objects.scene import Scene - scene = Scene(args.scale) - -if args.rewind_viewer: - from RewindClient import RewindClient - rewind_client = RewindClient() - - org_append_tick_to_game_log = Game.append_tick_to_game_log - - def append_tick_to_game_log(game: Game): - W2 = WIDTH//2 - TERRITORY_LAYER = 1 - BONUS_LAYER = 2 - GRID_LAYER = 3 - TRAIL_LAYER = 4 - PLAYER_LAYER = 5 - DIRECTION_DELTA = { - 'left': (-1, 0), - 'right': (1, 0), - 'up': (0, 1), - 'down': (0, -1), - } - SPRITES = { - 'n': ((-W2+3, -W2+3, W2-3, W2-3), (0, W2-3, W2-3, W2-3), (W2-3, 0, W2-3, W2-3)), - 's': ((-W2+3, W2-3, W2-3, -W2+3), (0, -W2+3, W2-3, -W2+3), (W2-3, 0, W2-3, -W2+3)), - 'saw': ((-W2+3, 0, 0, W2-3), (0, W2-3, W2-3, 0), (W2-3, 0, 0, -W2+3), (0, -W2+3, -W2+3, 0)), - } - - def color2rv(c): - return c[2] | (c[1] << 8) | (c[0] << 16) | (c[3] << 24) - - rc = rewind_client - for x in range(32): - rc.line(x*WIDTH, 0, x*WIDTH, 31*WIDTH, 0xffffff, GRID_LAYER) - for y in range(32): - rc.line(0, y*WIDTH, 31*WIDTH, y*WIDTH, 0xffffff, GRID_LAYER) - for p in game.players: - for (x, y) in p.territory.points: - rc.rectangle(x-W2, y-W2, x+W2, y+W2, color2rv(p.territory.color), TERRITORY_LAYER) - for (x, y) in p.lines: - rc.circle(x, y, int(0.1*WIDTH), color2rv(p.line_color), TRAIL_LAYER) - rc.circle(p.x, p.y, int(0.2*WIDTH), 0, PLAYER_LAYER) - rc.circle(p.x, p.y, int(0.2*WIDTH)-2, color2rv(p.color), PLAYER_LAYER) - dx, dy = DIRECTION_DELTA.get(p.direction, (0, 0)) - rc.line(p.x, p.y, p.x+dx*int(0.2*WIDTH), p.y+dy*int(0.2*WIDTH), 0, PLAYER_LAYER) - message = [f'Tick {game.tick}'] - for p in sorted(game.players + game.losers, key=lambda p: p.id): - message += ( - '#{} @{} score {:3}'.format(p.id, '(dead )' if p in game.losers else f'({p.x:3}, {p.y:3})', p.score), - '' if not p.bonuses else ' bonus: {}'.format(', '.join(f'{b.visio_name} ({b.get_remaining_ticks()} cells)' for b in p.bonuses)), - ) - pos = int(31.5*WIDTH), int((31.5-p.id)*WIDTH) - rc.circle(*pos, int(0.4*WIDTH), color2rv(p.color), PLAYER_LAYER) - rc.popup(*pos, int(0.4*WIDTH), p.name) - for b in game.bonuses: - for dx1, dy1, dx2, dy2 in SPRITES[b.visio_name]: - rc.line(b.x+dx1, b.y+dy1, b.x+dx2, b.y+dy2, 0, BONUS_LAYER) - rc.circle(b.x, b.y, int(0.1*WIDTH), 0, BONUS_LAYER) - rc.popup(b.x, b.y, int(0.4*WIDTH), b.visio_name) - rc.message('\n'.join(message)) - rc.end_frame() - org_append_tick_to_game_log(game) - Game.append_tick_to_game_log = append_tick_to_game_log - if args.replay: visio = json.load(gzip.open(args.replay)) assert(visio.get('visio_version', 0) >= 2) start_game = visio['visio_info'][0] assert(start_game['type'] == 'start_game') - # FIXME: load WIDTH, SPEED, etc from `start_game` + + CONSTS.SPEED = start_game['speed'] + CONSTS.WIDTH = start_game['width'] + CONSTS.X_CELLS_COUNT = start_game['x_cells_count'] + CONSTS.Y_CELLS_COUNT = start_game['y_cells_count'] + CONSTS.WINDOW_HEIGHT = CONSTS.Y_CELLS_COUNT * CONSTS.WIDTH + CONSTS.WINDOW_WIDTH = CONSTS.X_CELLS_COUNT * CONSTS.WIDTH last_tick = 0 BONUS_CLASSES = {bc.visio_name: bc for bc in Bonus.__subclasses__()} @@ -124,6 +63,7 @@ def send_game_tick(self: Game): pass org_send_game_tick(self) Game.send_game_tick = send_game_tick + Game.game_save = lambda self: None class ReplayClient(Client): def __init__(self, id): @@ -139,9 +79,12 @@ async def get_command(self): def get_solution_id(self): return visio['config'][self.id] clients = [ReplayClient(id) for id in sorted(visio['config'].keys())] -else: + +scene = Scene(args.scale) + +if not args.replay: clients = [] - for i in range(1, LR_CLIENTS_MAX_COUNT + 1): + for i in range(1, CONSTS.LR_CLIENTS_MAX_COUNT + 1): arg = getattr(args, 'player{}'.format(i)) if arg: if arg == 'keyboard': @@ -153,24 +96,8 @@ def get_solution_id(self): clients.append(client) -if args.no_gui: - game = Game(clients) - loop.run_until_complete(game.game_loop_wrapper()) - if args.replay: - for a, b in zip(visio['visio_info'], game.game_log): - if a.get('type', None) == 'end_game': - a.pop('events') # ignore events - if b.get('type', None) == 'end_game': - b.pop('events') # ignore events - if a != json.loads(json.dumps(b)): # json roundtrip to convert tuples to lists and int dict keys to strings - print("Replay '{}' failed on {}:{}".format(args.replay, a.get("type", None), a.get("tick_num", None))) - sys.exit(1) - else: - print("OK") - sys.exit(0) - -if len(clients) == 0: - clients.append(KeyboardClient(scene.window)) + if len(clients) == 0: + clients.append(KeyboardClient(scene.window)) class Runner: @@ -182,7 +109,8 @@ def game_over_loop(dt): @staticmethod def game_loop_wrapper(dt): is_game_over = loop.run_until_complete(Runner.game.game_loop()) - if is_game_over or (args.timeout == 'on' and Runner.game.tick >= MAX_TICK_COUNT): + if is_game_over or (args.timeout == 'on' and Runner.game.tick >= CONSTS.MAX_TICK_COUNT): + loop.run_until_complete(Runner.game.game_loop()) Runner.game.send_game_end() Runner.game.game_save() Runner.stop_game() @@ -203,10 +131,10 @@ def on_resize(width, height): glMatrixMode(gl.GL_PROJECTION) glLoadIdentity() - factScale = max(WINDOW_WIDTH / actual_width, WINDOW_HEIGHT / actual_height) - xMargin = (actual_width * factScale - WINDOW_WIDTH) / 2 - yMargin = (actual_height * factScale - WINDOW_HEIGHT) / 2 - glOrtho(-xMargin, WINDOW_WIDTH + xMargin, -yMargin, WINDOW_HEIGHT + yMargin, -1, 1) + factScale = max(CONSTS.WINDOW_WIDTH / actual_width, CONSTS.WINDOW_HEIGHT / actual_height) + xMargin = (actual_width * factScale - CONSTS.WINDOW_WIDTH) / 2 + yMargin = (actual_height * factScale - CONSTS.WINDOW_HEIGHT) / 2 + glOrtho(-xMargin, CONSTS.WINDOW_WIDTH + xMargin, -yMargin, CONSTS.WINDOW_HEIGHT + yMargin, -1, 1) glMatrixMode(gl.GL_MODELVIEW) return pyglet.event.EVENT_HANDLED @@ -230,7 +158,7 @@ def run_game(): Runner.load_sprites() Runner.game = LocalGame(clients, scene, args.timeout == 'on') Runner.game.send_game_start() - pyglet.clock.schedule_interval(Runner.game_loop_wrapper, 1 / 200) + pyglet.clock.schedule_interval(Runner.game_loop_wrapper, 1 / 40) Runner.run_game() diff --git a/paperio/local_runner/requirements.txt b/paperio/local_runner/requirements.txt index 7b61722..da9da28 100644 --- a/paperio/local_runner/requirements.txt +++ b/paperio/local_runner/requirements.txt @@ -1,3 +1,2 @@ pyglet==1.3.2 -networkx==2.3 -pytest==5.0.1 +networkx==2.3 \ No newline at end of file diff --git a/paperio/local_runner/tests/e2e/000.visio.gz b/paperio/local_runner/tests/e2e/000.visio.gz deleted file mode 100644 index fa18ee5a46f049bfa31b1f8c92d7fa591fa7235d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13442 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 diff --git a/paperio/local_runner/tests/e2e/001.visio.gz b/paperio/local_runner/tests/e2e/001.visio.gz deleted file mode 100644 index d7d9dad9ad609f313729f68981ccc36a27d63302..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44268 zcmZ^~cQ~8j+yAev)zWH9%^Ibs+N(BIYLB9J?Y*fPyA-u|ZCVsXq_!GS)E+U4T2W%h zj@Xgko%Z=X-_LhE|D5-Ao$vSibzVpABkg@i!Wc_TjLw!exsLU5@N{s)+g@0Bi_rVX zGpoN@hHN!BB^s%Cy}hw9v06i4+OXsP#djwmA^0S9Ky54yU+FP%n1NG5j_b!SuS*`^ z_Y7Q2;lp0=V%OJ0t}c=7*aF62>>)PzY!HibLzjkJ42E2t4=#pW9SmYm_u5~ZUAgJj z2ccGFrp*{jw>C|(A&F5?>|qn;yvZT|D&Pct@LReq1dH}C)5W4zLjo@Xg0EKh+Jny^ zA(*}Pz$?gN@af?OFZS}1u|4GKgb|Af!R}}9?x91l7@3gMUYV=2J?t5h5wISvm7E_k zVx4wnu27AP!KZT>*s&ncC8Pj*d{$a2Qp_7NQNVbPf??0FQbD7OR|kw(^eRdsNE(aD z2)SB^w9B-qo*z!NV=gbquxDp;?AVNu%l#9WX2=SPtQ~uWS~m;9LP|rn%_`Og7qzDs zu;)i^i;TW@iM?)#7quswFnFm1;;HFp_P;LatC3M|OJ?1#K{|W9f;R3$?|? zb%Z8zuD}oFAnYU!K1Ry4pPg%-9yiGZNCpMW7qn@H97|&WqurihUK{KP69mW9jYx0h zXjYhD9CYmp^t_M%hYMAZ(d1?6B>1c@vUYmP{dksk#MHQJ#>q)Ez~#0b1iZ9eFPz)f z5E%<}B_QBnbSWAm%#Z8*zwL$7Q{Rqe&kOXiOB0P1tL@msUTko)y^M(&7GVZ6dlv>N z`#Qww_!JeBJiAbvL8;a8Pfk&g{+J?BK(!qcK>7FA{cHYQmL zI|D+F`XyQ|x(Y6UeGagHbokYvR@4AExF^bXo)-E1vjZEKECrWAAxGm9tvOwV9g)_; ztDWf(%-i3bjyfe;Ig<)`SXcU$nAyr_TM`9R2uPVa;0N%v17&iRzyWt-vTs5E9k7Yh z5zzE#yh*)Zy2J`Pntc0v(g$|Gn$?P?D&oYun+qv{jrqT0kCb_5DS-89>Qz4Td{y^Q z0^d?_G-lJDTTU`ckN)Mjz^krX&GWUJqyDMESC=dmmslM~-4g8zZxyhdi8fMLC1`zO z7L#&5{WK^fL!`jmXlD}lekz6aLYtr2gw-3~m^#x$^6O8x-`TuqY5mUhg?ZzA2bCGn zaod63GRHuF{`L6-kq5rrnDdE>FQy(kn?Ps(fp4D2+$;V0axcX3DorEz?m(+t z<%MPXFYMO&4=yeT?7cYtp^8SCp^s9s+MQ3R@LHfpwd!^mb4Ob>K}jzuSxuWrSdKS2 zo7`_R4*WVsA;c{6;O-doWuE3G(=@8IcW1mlXo0e^nIzaH#I9N|dHT3PEzOsMprxU- zZ2Fi2J)2+E)^IWg+3A>v`$U+|g@12L+&bnjpS!47kj8Is@CEu0wvL}>&RvwbG8Q$r zwa@NY9XSQ?HTp(0x7}Jh<|&_xDs$bd^0}b6oP`s42AGx2A%KMY$6QCcO7(4C_~G2GIWk$VvQvT3cdVTqEyT^-rdhEx4|XQ$Lv<}f$Q*XjF7%v zazBM(CbJbF_VzPd0pb8Nq_pozY-=|2v~BXUEM`@m$J&&e2tRZxU_SaL!|R5*;R_)2vqj7qFRU& zg>*C$OS{i%*uHQjjI)qstS9nI*fE!;PaqsT*6=SZ{%Gw?f>`kt3k$xf}eD#3in))^JSFG3a$Ud1~H==!>jxQt=TH<}PZ*S8~D3qD? z6NmYAmHVTy*B)Qvz60bf@W86igS)XQ*IpB;JeRwb8JdFcB^^q5)7}AZ^8V;H9_@HD zzE{`6wevxq7ra$i$a*Jh(PsB{W)+|Jg}LG2~9Ozih8u~FPcjhkNS@Gf0es71NU^bcoD6%NHlhmsKZ@Z z{e~aRHnBC}kZ!47LE&q}I^h^>6yINGl&cpni9V<+*R!eM$(xw3OsF(8n;U@#^h|7wOvJ#JHWmD?4YW2D5e~xrdV>??>0s%6J#@3PZVoQ|2na9R zR>=PE;X%E42k+Iom~?Pt(~M&hn6gAS{e8u%$K*|Y#>5I+lO{)zhx#ZHdM@WT=;}^g zMv*t@&dC`?jo2b5_3`YGw-jQn)?kq?6`VAt`zChBdvL=#*AEMtI=R?u}@OACJC?n*f&L5;~nkHhr9AcSH|Y_ ziAQgz$m*$DtA?vF8}s)y1F8@%OTm_0??GQT1_%lq^*IX$lVBFmNxrnbhE%TKw1TH+&kL*Au2FGMaz$cAhyPgN#ZqY7LVQY*g; zrKnSB*7*1o=8|Qr+AhrC(~hv2G`rIBXjWFNuXcVgPMxGY24@H_r&Jyve2~Bh+Vw7! z&rj+1EX0^SB@x-xp7+RzYe`2`srM`dAV&7`!Hm9L&0hL+zMQ+K_nhZV>e?zsJw#M| zD|++1PT)NMKe~k%d_(;jiA&q-_?zZurE*^zb#}j)VoM?>%#9yImkF3x;Fbuq=^K0>+l+E zA;U_cdRxZA=Y(Hz_+sd=!eaebH+RL4PKsvqTxfKMju|H!Oc$iR*b5-$9FOYtOQn77 z&diU3vb^meM;UT_23N@N^>&3TV`2P&v%R86P3|-xG5m_H6pmBNUv&^goqE2o$VQfp zZA&ldWz)ElSaw(M)g!Q;+HypepZ7yAk_@z9^-W6Bs6QRw?vYLp%XjP}vNDH$UJU)* z-OAmN<`o(GX)fkLoKNl~<}sjD7Nh1IzWS5jPor}6r?;S% z#-$(IK(udmG-gt4jNZyMGrHsLGPNYT-rME!tltCCj3Yl~HCm_DTE*F$(}&KpeiIBv z>+I(m^hYCY3v*8o)Y8cJgvg^RJeNP7WM*C5Nf)>)BQ^Y^dx@HhPov@nqH{f9e)AMl zkOU8&o|9=`n;z-+{czVt<5>+N%Cp8SoqqkEtu{0}O~16azj)qOo9D7nD?0TM*=C?V zQ2NSR8!p7LQ|#xf&6aw|kSYMqO0yE+Xv|JC$yMgi36{|oiBkqm6sK`4=A{X&ny{@lt9@T>^IB_HWE~mMEInuv6^T{P zT!b=f?g<2t&w7)4-7yr4gH4zTOQ`5ajhsBc=lJoNllFS2KJT8zIi_HKXx>$VJLF5; zBwep#J5Q)j&(@li@Jsp%bpbuI#7W)u9EZwa?5dg-I;`4=)e7xuLZ{$XE|osW@h$U8S82Vjl1V+IAVEDR2Xv(I2yF#b+@z6F+oFL< zhMuC8H?n&6y?O<>?%bzRr9#A6gkO)Breb_+p~ta>2dwe;6wXFF#gigd^~ri}2E`7Q zrhIyI4(MPjZ(XCjlxly30p^c&aVHG@ON^8+FXty&%`rUrF zgk{0z3KkOK&|*1zs~R*79}^sIjxh; zL&%a>CwZ*`dyRARgRQ*mZoH_foYvH#7R$q9fjtb{Uh_~uS5Q&@RgbFcWZuF?Q2rN~ zV1sl{wQ9Ri-D$ko#atojqGliDLYVehj#5BGX?PJzcmO>4tbS8_4% zU}t!A>!7>_+u@9Sl{_MY9Hhi{I1t_HIGh-Lx!yoaex^YVGErbqUy)*a%gWN8*(Y#V z5zWN{$p;p<$qf~t)AGBe-ZUVAWZx{HbtYeBvjR}klBYFRG!#r_k`-yrg2_R;3RT(y zDJRtZ%1XifDG@9BEQU&^Qow>vp(ZJ?FF@=g=+o2jcTknuYy}U$)nJ+^MPD^_+%SUGEYwOS+1#Cf4h)tSr zYGX^{6`Z#aLXGL)Xm>t}J*A7kQuy6|vcZ@#7E&-4f^n9J6lljQgs`I*1CBJc zv3Uwubk4MsZ{M8%QAifrjw(NcxJd|`n02`eM^UYVW~U)n7pV8`*0+b3JukN1x0ojjClwfUEf&DUW-@ zY?n+8nxKa6h##15qmPF>{kiNZtGdE$Ej)pRPw$H_hE3#@CCD5CU)FhYlI(~{18QLs zeG7|UT+Zjj^QF@{T^ppjmKOae(>>LUzF5^rpqYsS-_dGK0EF~DP07^lclG;UL=9m!+W zCg4Tms~gFK_&i|mP2;QA)64^)4)|;~1g4%8P1scGDU=R2l?oV{^jM{T9&CaL^mv;n z=xFs6rD-7r^f=@)s!eH{<5~dx!7JP#2~I1U3@x1=O!~nle(Xz+%1z;q!B~;IVza{C zJ*~e~*n1}sKGBTe6f3!4$q?FF3I{zZ29%j2wP8=+rbi<&Il8xm4|G*$n=DXS`GQtS z?7DU~KF9Q*;p07&Q7CG z(st5Lc$cPEAr1+aFUB*)O)guHlExjht6r*rm1k%HuT)Z@vKX

cWwWlf54^InUBAE%O&gUBJ&|F6+r=w5xD( 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 diff --git a/paperio/local_runner/tests/test_e2e.py b/paperio/local_runner/tests/test_e2e.py deleted file mode 100644 index cf8ad01..0000000 --- a/paperio/local_runner/tests/test_e2e.py +++ /dev/null @@ -1,8 +0,0 @@ -import pytest -import subprocess -import glob - -@pytest.mark.parametrize("arg", glob.glob('tests/e2e/*.visio.gz')) -def test(arg): - ret = subprocess.run(["python3", "localrunner.py", "--no-gui", "--replay", arg]) - assert 0==ret.returncode From e73925bbdd558d9c1c0b6edecf03de344ffcd8e7 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Wed, 7 Aug 2019 17:18:18 +0300 Subject: [PATCH 94/96] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20Dockerfile=20=D0=B8=20serverrunner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/local_runner/Dockerfile | 17 ++++++++++ paperio/local_runner/serverrunner.py | 48 ++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 paperio/local_runner/Dockerfile create mode 100644 paperio/local_runner/serverrunner.py diff --git a/paperio/local_runner/Dockerfile b/paperio/local_runner/Dockerfile new file mode 100644 index 0000000..1d89c6c --- /dev/null +++ b/paperio/local_runner/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:18.04 + +WORKDIR /opt/mechanic + +RUN apt-get update && \ + apt-get install -y python3.6 python3-pip && \ + apt-get clean && \ + apt-get autoclean && \ + apt-get autoremove + +COPY requirements.txt ./requirements.txt +RUN pip3 install -r requirements.txt + +COPY . ./ + +EXPOSE 8000 +CMD ["python3", "-u", "./serverrunner.py"] \ No newline at end of file diff --git a/paperio/local_runner/serverrunner.py b/paperio/local_runner/serverrunner.py new file mode 100644 index 0000000..0a1d3d3 --- /dev/null +++ b/paperio/local_runner/serverrunner.py @@ -0,0 +1,48 @@ +import asyncio + +from clients import TcpClient +from game_objects.game import Game +from constants import CONSTS + + +class GameServer: + def __init__(self): + self.clients = [] + + async def connection_handler(self, client_reader, client_writer): + client = TcpClient(client_reader, client_writer) + is_success = await client.set_solution_id() + + clients_count = len(self.clients) + + if clients_count < CONSTS.CLIENTS_COUNT: + if is_success: + clients_count += 1 + print('{} clients connected'.format(clients_count)) + self.clients.append(client) + else: + loop.stop() + else: + client_writer.close() + + game_future = None + if clients_count == CONSTS.CLIENTS_COUNT: + game = Game(self.clients) + game_future = asyncio.ensure_future(game.game_loop_wrapper()) + + if game_future: + done, pending = await asyncio.wait([game_future]) + if not pending: + loop.stop() + + print('game done') + + +gs = GameServer() + +loop = asyncio.get_event_loop() +loop.run_until_complete(asyncio.start_server(gs.connection_handler, '0.0.0.0', 8000)) +try: + loop.run_forever() +finally: + loop.close() From ea2004ca031b5c1837bc2b04fbc26d382d31dbd5 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Fri, 9 Aug 2019 22:43:10 +0300 Subject: [PATCH 95/96] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=B9=D0=BD?= =?UTF-8?q?=D0=B5=D1=80=D1=8B=20=D0=B8=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/dockers/java1.11/Dockerfile | 19 +++++++ paperio/dockers/java1.11/pom.xml | 61 +++++++++++++++++++++++ paperio/dockers/nodejs12/Dockerfile | 9 ++++ paperio/local_runner/game_objects/game.py | 10 ++-- paperio/local_runner/localrunner.py | 2 +- paperio/local_runner/serverrunner.py | 2 + 6 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 paperio/dockers/java1.11/Dockerfile create mode 100644 paperio/dockers/java1.11/pom.xml create mode 100644 paperio/dockers/nodejs12/Dockerfile diff --git a/paperio/dockers/java1.11/Dockerfile b/paperio/dockers/java1.11/Dockerfile new file mode 100644 index 0000000..8578463 --- /dev/null +++ b/paperio/dockers/java1.11/Dockerfile @@ -0,0 +1,19 @@ +FROM stor.highloadcup.ru/aicups/paperio_base +MAINTAINER Boris Kolganov + +RUN add-apt-repository -y ppa:openjdk-r/ppa && \ + apt-get update -y && \ + apt-get install -y openjdk-11-jdk && \ + apt-get install -y maven + +ENV SOLUTION_CODE_ENTRYPOINT=Main.java +ENV COMPILED_FILE_PATH=/opt/client/javaStrategy.jar +ENV SOLUTION_CODE_PATH=/opt/client/src/main/java/ +ENV COMPILATION_COMMAND='mvn package -q' +ENV RUN_COMMAND='java -jar $MOUNT_POINT' +ENV MAVEN_OPTS='-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStorePassword=changeit' + +COPY pom.xml ./ +RUN mkdir -p src/main/java && mvn dependency:go-offline && \ + mvn package && \ + rm -rf javaStrategy.jar target/classes/ \ No newline at end of file diff --git a/paperio/dockers/java1.11/pom.xml b/paperio/dockers/java1.11/pom.xml new file mode 100644 index 0000000..af19306 --- /dev/null +++ b/paperio/dockers/java1.11/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + JavaStrategy + JavaStrategy + 1.0 + + + UTF-8 + 11 + ${java.version} + ${java.version} + + + + javaStrategy + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + + + + maven-jar-plugin + 3.1.2 + + ${basedir} + + + Main + true + ${settings.localRepository} + repository + + + + + + + + + + org.json + json + 20180130 + + + + com.google.code.gson + gson + 2.8.5 + + + + \ No newline at end of file diff --git a/paperio/dockers/nodejs12/Dockerfile b/paperio/dockers/nodejs12/Dockerfile new file mode 100644 index 0000000..514cf37 --- /dev/null +++ b/paperio/dockers/nodejs12/Dockerfile @@ -0,0 +1,9 @@ +FROM stor.highloadcup.ru/aicups/paperio_base +MAINTAINER Boris Kolganov + +RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && \ + apt-get install -y nodejs + +ENV SOLUTION_CODE_ENTRYPOINT=main.js +ENV SOLUTION_CODE_PATH=/opt/client/solution +ENV RUN_COMMAND='/usr/bin/node $SOLUTION_CODE_PATH/$SOLUTION_CODE_ENTRYPOINT' diff --git a/paperio/local_runner/game_objects/game.py b/paperio/local_runner/game_objects/game.py index 57d1286..1a04358 100644 --- a/paperio/local_runner/game_objects/game.py +++ b/paperio/local_runner/game_objects/game.py @@ -156,7 +156,7 @@ def send_game_end(self): for player in self.players: player.send_message('end_game', {}) - def send_game_tick(self): + def append_tick_to_game_log(self): self.game_log.append({ 'type': 'tick', 'players': self.get_players_states(), @@ -164,7 +164,9 @@ def send_game_tick(self): 'tick_num': self.tick, 'saw': Saw.log }) + Saw.log = [] + def send_game_tick(self): for player in self.players: if (player.x - round(CONSTS.WIDTH / 2)) % CONSTS.WIDTH == 0 and (player.y - round(CONSTS.WIDTH / 2)) % CONSTS.WIDTH == 0: player.send_message('tick', { @@ -173,8 +175,6 @@ def send_game_tick(self): 'tick_num': self.tick, }) - Saw.log = [] - async def game_loop_wrapper(self, *args, **kwargs): self.send_game_start() while True: @@ -221,6 +221,8 @@ async def game_loop(self, *args, **kwargs): if futures: await asyncio.wait(futures) + self.append_tick_to_game_log() + for player in self.players: player.move() @@ -323,7 +325,7 @@ def get_players_external_id(self): def save_visio(self): d = { - 'visio_version': 2, + 'visio_version': 3, 'config': self.get_players_external_id(), 'visio_info': self.game_log } diff --git a/paperio/local_runner/localrunner.py b/paperio/local_runner/localrunner.py index d85d17e..806f5a4 100644 --- a/paperio/local_runner/localrunner.py +++ b/paperio/local_runner/localrunner.py @@ -158,7 +158,7 @@ def run_game(): Runner.load_sprites() Runner.game = LocalGame(clients, scene, args.timeout == 'on') Runner.game.send_game_start() - pyglet.clock.schedule_interval(Runner.game_loop_wrapper, 1 / 40) + pyglet.clock.schedule_interval(Runner.game_loop_wrapper, 1 / 200) Runner.run_game() diff --git a/paperio/local_runner/serverrunner.py b/paperio/local_runner/serverrunner.py index 0a1d3d3..73645b3 100644 --- a/paperio/local_runner/serverrunner.py +++ b/paperio/local_runner/serverrunner.py @@ -1,3 +1,4 @@ +import random import asyncio from clients import TcpClient @@ -27,6 +28,7 @@ async def connection_handler(self, client_reader, client_writer): game_future = None if clients_count == CONSTS.CLIENTS_COUNT: + random.shuffle(self.clients) game = Game(self.clients) game_future = asyncio.ensure_future(game.game_loop_wrapper()) From 9639e74b27a3c22b61aa9d5613e38a0c7b94e5b7 Mon Sep 17 00:00:00 2001 From: Kislenko Maksim Date: Fri, 9 Aug 2019 22:45:00 +0300 Subject: [PATCH 96/96] =?UTF-8?q?=D0=94=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=20=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- paperio/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/paperio/README.md b/paperio/README.md index a4e771b..2f558bc 100644 --- a/paperio/README.md +++ b/paperio/README.md @@ -200,6 +200,8 @@ Local Runner написан на языке программирования Pyt **Внимание!** Под командой запуска подразумевается полная команда, используемая для исполнения решения-стратегии. Частой ошибкой участников является попытка использовать, например, `main.py` вместо `python main.py` или `main.cs` вместо пути к скомпилированному бинарному файлу. +**Внимание!** При использовании аргументов `-p{num}` или `--player{num}` каждому `num` будет соответствовать неизменное первоначальное положение игрока. На сервере начальные положения игроков распределяются случайным образом. + **Важно!** Не стоит забывать про буферизацию ввода/вывода в используемом инструментарии (например, интерпретатору python нужно передать флаг `-u` или выставить соответствующую переменную окружения). Без этого корректная работа не гарантируется. Пользователи Windows могут столкнуться с проблемой, когда интерпретатор языка программирования установлен не совсем верно, и его нет в системной переменной `PATH`. В таком случае необходимо указывать полный путь к интерпретатору вашего языка или добавлять его в `PATH`.