diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac5e136 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +*.a +build/ +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +cmake_uninstall.cmake +*Config.cmake +*ConfigVersion.cmake +core/config.h +CPackConfig.cmake +CPackSourceConfig.cmake +install_manifest.txt +Makefile +*.pb.cc +*.pb.h +*Targets.cmake +_CPack_Packages/ +.deb +doc/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..13a11d0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,72 @@ +sudo: required + +language: cpp + +compiler: + - clang + - gcc + +# N.B. - The P4 cmake configure step under Travis will install all of its dependencies, +# some of which overlap with our dependencies. Notably: +# - nanomsg +# - nnpy +# - libpcap +before_install: + # Before anything else, lint it to make sure everything's good + - sudo pip install cpplint + - .travis/run_lint.sh + # Copy pasted from https://github.com/jdm64/saphyr/blob/8808c34dec4f94f86a8dd6a832037cba4ba2d88a/.travis.yml + - sudo cp /etc/apt/sources.list /etc/apt/sources.list.d/trusty.list + - sudo sed -i 's/precise/trusty/g' /etc/apt/sources.list.d/trusty.list + - sudo apt-get update -qq + - sudo apt-get install -qq -y --force-yes flexc++ bisonc++ protobuf-compiler libprotobuf-dev python-setuptools + # + # Install SystemC + - wget http://www.accellera.org/images/downloads/standards/systemc/systemc-2.3.1.tgz + - tar -xzf systemc-2.3.1.tgz + - cd systemc-2.3.1 + - ./configure --prefix=/usr --with-unix-layout + - make + - sudo make install + - cd .. + +# Build and install our packages +install: + - cmake . + - make + - sudo make install + +# Test that the libraries was installed correctly +script: + # Test that both are installed properly + # TODO(gordon) - (cd tests/PFP-P4 && cmake .) + # TODO(gordon) - (cd tests/PFPSIM && cmake .) + # Test that a simple PFPSIM project works + - (mkdir -p tests/simple-pfp/simple/build && cd tests/simple-pfp/simple/build && cmake ../src && make && ./simple-sim -v debug) + # Test uninstall + - sudo make uninstall && sudo rm install_manifest.txt + # TODO(gordon) - (cd tests/PFP-P4/ && ./check-cmake-fails) + # TODO(gordon) - (cd tests/PFP-P4/ && ./check-cmake-fails) + # Test making deb package and installing it + - make package + - sudo dpkg -i pfpsim-$VERSION_NUMBER-Linux.deb + # Test that both are installed properly + # TODO(gordon) - (cd tests/PFP-P4 && cmake .) + # TODO(gordon) - (cd tests/PFPSIM && cmake .) + # Test that a simple PFPSIM project works + - (mkdir -p tests/simple-pfp/simple/build && cd tests/simple-pfp/simple/build && cmake ../src && make && for i in {1..100}; do ./simple-sim -v debug; done) + +before_deploy: + - mv pfpsim-$VERSION_NUMBER-Linux.deb pfpsim-$VERSION_NUMBER-Linux-$CXX.deb + +env: + global: + - VERSION_NUMBER=$(.travis/get_version_from_tag.sh complete) +deploy: + provider: releases + api_key: + secure: "PwoOnJFSC/x1hW2zJ0oicOKSGz44gurQvMmMsg/bKgbzJ66xHzu6CuEOa/y9oMbb/zu6za+Cm2wbU4hTuvQIUA17zEiTfOw8hwSgH0Iu8l8Go7G3FqjduiWC/1o9yzZ3Zanyz/HD97LzlWUIN2FdfrtHjivazIPlaVt4/L6qTUlNdmue6mt3F8qU2sjSn1ibvYsXy4+2fm0sDY+/fkvk57+Y8SAgAfrUHvx0rFFJZ/zyTCXH0NKPbXQT+2yq5h4DMpp4+dq/q9dUcbdLATAZ/nmhfOWJ+fWRj0YMsteKm1vzZFt6DbYeL5h+GHu9ZqJs6fOw47BOjut+TyRf8+eASIqk6stkrH29aroVAYYeQbbj34HJrOIHshpvrwU3t+wVVKeP3CvXa/LciQB73MrZNgx2PkfTDSYmsUPM4qAsrQ+79iMk3UbRVp2WLIVJKMADnZuQS+Vyn9GXH7wg+hpt7kuoK2lvqHPFsln9/drQARnWvTayq9LfNjENfb8i/NfGpe3Rks2kzpCsj3Ti27f6zKf8XciGxrrEBvRvHP6Ho5RBWdZk7D1lPE+QkFba/5N7Mcwx0eIKZHFxvtsExagIIvOQHmjTqHuqr+UOc4htJPituuJ3aIoM5l47mOw4p3oUlB19JckV8RsrI+0BrQYURIaOvd1ttZuXMMoeU7upGow=" + file: pfpsim-$VERSION_NUMBER-Linux-$CXX.deb + skip_cleanup: true + on: + tags: true diff --git a/.travis/get_version_from_tag.sh b/.travis/get_version_from_tag.sh new file mode 100755 index 0000000..bd85c66 --- /dev/null +++ b/.travis/get_version_from_tag.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# +# PFPSim: Library for the Programmable Forwarding Plane Simulation Framework +# +# Copyright (C) 2016 Concordia Univ., Montreal +# Samar Abdi +# Umair Aftab +# Gordon Bailey +# Faras Dewal +# Shafigh Parsazad +# Eric Tremblay +# +# Copyright (C) 2016 Ericsson +# Bochra Boughzala +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + + +# If a tag is not set, return 0 +if [ -z "$TRAVIS_TAG" ] +then + complete=0.0.0 + major=0 + minor=0 + patch=0 +else + # Split the version number on dots, storing it in an array + complete=$(echo $TRAVIS_TAG | tr -d v ) + major=$(echo $complete | awk 'BEGIN {FS="."} {print $1}') + minor=$(echo $complete | awk 'BEGIN {FS="."} {print $2}') + patch=$(echo $complete | awk 'BEGIN {FS="."} {print $3}') +fi + + +case $1 in + major) echo -n $major;; + minor) echo -n $minor;; + patch) echo -n $patch;; + complete) echo -n $complete;; + *) echo -n $complete;; +esac diff --git a/.travis/run_lint.sh b/.travis/run_lint.sh new file mode 100755 index 0000000..8b06a80 --- /dev/null +++ b/.travis/run_lint.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# +# PFPSim: Library for the Programmable Forwarding Plane Simulation Framework +# +# Copyright (C) 2016 Concordia Univ., Montreal +# Samar Abdi +# Umair Aftab +# Gordon Bailey +# Faras Dewal +# Shafigh Parsazad +# Eric Tremblay +# +# Copyright (C) 2016 Ericsson +# Bochra Boughzala +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + + +# cpplint +# Script for running cpplint on a directory of code (.h and .cpp files only) +# +# Author: Eric Tremblay +# Created on: April 25th, 2016 +# +# Usage: ./run_lint.sh [pattern] +# The optional pattern is used to grep the output. Useful if looking to fix a particular category of errors. + +# Filters: -x --> ignore category x +# +y --> don't ignore category y +# Separate each by a comma with no space +# Example: -whitespace,+whitespace/braces --> ignores all 'whitespace' errors except the 'whitespace/braces' errors. +filters="-legal/copyright,-runtime/references,-build/include_subdir,-build/c++11" + +# Root Directory: Used to determine appropriate header guards +# The path must be relative to the location of the directory containing the .git file +# Do not put a / at the end +# Example: rmt/src --> Old header guards: RMT_SRC_BEHAVIOURAL_LOGGER_H_, New header guards: BEHAVIOURAL_LOGGER_H_ +root_dir="pfpsim" + +# Lint Directory: Directory in which to start linting. The path is relative to where this script is run. +# Examples: ../src or . +lint_dir="pfpsim" + +# Exclude Directories: Directories to exclude. Directories should be specified relative to the Lint Directory. +# Use regex syntax. In particular, must escape all . and all /. Use | to separate multiple directories +# Use NO_EXCLUDE_DIRS if there is nothing to exclude. +# Example: \.\.\/build\/|\.\.\/src\/tries\/ --> excludes ../build and ../src/tries/ +exclude_dirs="pfpsim\/doxygen\/|pfpsim\/core\/proto\/|pfpsim\/core\/cp\/CommandParser.cpp|pfpsim\/core\/cp\/CommandScanner.cpp|pfpsim\/core\/cp\/CommandParserbase.h|pfpsim\/core\/cp\/CommandScannerbase.h|pfpsim\/core\/cmake\/" + +# Lint Command: Command to run cpplint. Should not have to be modified. +lint_command="cpplint --root="${root_dir}" --counting=detailed --filter="${filters}" $( find "${lint_dir}" -name \*.h -or -name \*.cpp | grep -vE "${exclude_dirs}" )" + +if [ $# -eq 0 ]; then + ${lint_command} +else + ${lint_command} 2>&1 | grep --color "$1" +fi diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d5053fb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,61 @@ +# +# PFPSim: Library for the Programmable Forwarding Plane Simulation Framework +# +# Copyright (C) 2016 Concordia Univ., Montreal +# Samar Abdi +# Umair Aftab +# Gordon Bailey +# Faras Dewal +# Shafigh Parsazad +# Eric Tremblay +# +# Copyright (C) 2016 Ericsson +# Bochra Boughzala +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + +project(pfpsim) + +cmake_minimum_required(VERSION 2.8.7) + +#--------------OPTION FLAGS------------------------------------------------ +option(PFPSIM "PFPSIM" ON) # cmake -DPFPSIMCORE=ON|OFF +option(PFP-P4 "PFP-P4" ON) #cmake -DPFP-P4=ON|OFF + +#--------------Add Subdirectories------------------------------------------------ +if("${PFP-P4}" MATCHES "ON") + message (STATUS "Install PFP-P4? Yes") + add_subdirectory(pfp-p4) +else () + message (STATUS "Install PFP-P4? No") +endif() + +if("${PFPSIM}" MATCHES "ON") + message (STATUS "Install PFPSIM? Yes") + add_subdirectory(pfpsim) +else () + message (STATUS "Install PFPSIM? No") +endif() + +#------------------------ uninstall target ----------------- +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + +add_custom_target(uninstall +COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bcdb36d --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a99041 --- /dev/null +++ b/README.md @@ -0,0 +1,200 @@ +# PFPSim [![Build Status](https://travis-ci.org/pfpsim/PFPSim.svg?branch=master)](https://travis-ci.org/pfpsim/PFPSim) + +This repository contains the core PFPSim library of common utilities, base classes, and SystemC support infrastructure which PFPSim-based projects depend on. Additionally, it links to +a fork of [P4, an emerging programming language for packet processing applications](http://p4.org), which can be +used to program the applications that run on PFPSim models. + +The PFPSim methodology and workflow is +[described in detail here](https://github.com/pfpsim/pfpgen#pfpsim-methodology--workflow). + +### Contents +- [Installation](#installation) + - [Using the PFPSim GUI Installer](#using-the-pfpsim-gui-installer) + - [Using Prebuilt Package](#using-prebuilt-package) + - [Building from Source](#building-from-source) + - [Dependencies](#dependencies) + - [Building](#building) + - [Installing](#installing) + - [Building Package](#building-package) + - [Building API Docs](#building-api-docs) +- [Running Tests](#running-tests) +- [Support](#support) +- [Contributing](#contributing) +- [License](#license) +- [Authors](#authors) + +# Installation + +## Using the PFPSim GUI Installer +We provide [a text-based GUI installer](https://github.com/pfpsim/pfpsim-installer) +for the PFPSim library, compiler, debugger, and required dependencies. This is +the recommended method for installing this library. + +## Using Prebuilt Package +We provide pre-built deb packages on [our releases page](https://github.com/pfpsim/PFPSim/releases). These packages +contain both the PFPSim core library, and the PFP P4 fork. They can be installed on Debian-based distributions using + +``` +sudo dpkg -i pfpsim-X.Y.Z-Linux-COMPILER.deb +``` + +Currently our automated releases upload packages tagged with the name of the compiler used to build them. +Both the `clang (clang++)` and `g (g++)` packages are built against `libstdc++-4.8` and both should work +regardless of which compiler you use, but we have not tested this extensively. + +To use the installed library, it's still necessary to install most of the [dependencies mentioned below](#dependencies). +The build-time dependencies (flexc++, bisonc++, protobuf-compiler) are no longer needed, but all the rest are. + +## Building from Source + +### Dependencies + +You will need CMake and a C++11 compliant compiler to build PFPSim. More information about CMake and build systems +that we like and how to install them +[can be found on our wiki](https://github.com/pfpsim/PFPSim/wiki/Build-managment-[CMAKE-Make-Ninja]). + +The following required dependencies can be installed using `apt-get` under Debian or Ubuntu: + +- libprotobuf-dev +- bisonc++ +- flexc++ +- protobuf-compiler + +Additionally SystemC and nanomsg must be installed from source: + +Due to SystemC's licensing terms, it must be downloaded manually. Download it from +[this page](http://accellera.org/downloads/standards/systemc). Follow the link for "SystemC 2.3.1 (Includes TLM) | Core SystemC Language and Examples". + +Once you have the archive downloaded, SystemC can be built and installed as follows: + +```sh +tar -xzf systemc-2.3.1.tgz +cd systemc-2.3.1 +./configure --prefix=/usr --with-unix-layout +make +sudo make install +``` + +Next we need to install [nanomsg](http://nanomsg.org/). It can be downloaded, built and installed as follows + +```sh +wget http://download.nanomsg.org/nanomsg-0.5-beta.tar.gz +tar -xzf nanomsg-0.5-beta.tar.gz +cd nanomsg-0.5-beta +./configure +make +sudo make install +``` + +If you also plan on installing the P4 library, there are some additional required dependencies. More information +is available [in the `p4-behavioral-model` repository](https://github.com/pfpsim/p4-behavioral-model#dependencies). + +### Building + +Once all of the dependencies are installed, building from source is straightforward. + +``` +git clone https://github.com/pfpsim/PFPSim.git +mkdir PFPSim/build +cd PFPSim/build +cmake .. +make +``` + +By default, both the PFPSim Core library, and the PFPSim P4 fork are built. To select which components to build, the +following flags may be passed to cmake: + +- `-DPFPSIMCORE=ON` or `-DPFPSIMCORE=OFF` to enable or disable building PFPSim core +- `-DPFP-P4=ON` or `-DPFP-P4=OFF` to enable or disable building the PFPSim P4 fork + +### Installing + +Once the project is built, it can be installed with `sudo make install`. In order to selectively install +components, they should be enabled or disabled during the build step as specified above. + +### Building Package + +[CPack](https://cmake.org/Wiki/CMake:Packaging_With_CPack) is used to generated various source/pre-built +packages which make it possible to install PFPSim without building from source. + +Once the project is built, packages can be generated with `make package`. Because we use Travis CI to +automate building and deploying releases, the version number of the generated package is controlled +by the `TRAVIS_TAG` environment variable. If you wish to build a package with a specific version number, +then in the build step above, replace the `cmake` command with `TRAVIS_TAG=vX.Y.Z cmake .. ` where X.Y.Z +is your desired version number. + +### Building API Docs + +We use [doxygen](http://www.stack.nl/~dimitri/doxygen/) to generate API docs. The following dependencies +can be installed from `apt-get` + +- doxygen +- graphviz + +Then docs can be built with `make doc` + +# Running Tests + +To validate your installation, we provide a very simple test project. It can be built as follows: + +```sh +cd tests/simple-pfp/simple/ +mkdir build +cd build +cmake ../src +make +``` + +Running the simulation model with `./simple-sim -v debug` should then produce the following output: + + +``` + SystemC 2.3.1-Accellera --- Feb 17 2016 16:19:54 + Copyright (c) 1996-2014 by all Contributors, + ALL RIGHTS RESERVED +config root is: ./Configs/ +Verbostiy Level is: debug +Output dir is: ./ +0.000 ns @ Got: 0 +10.000 ns @ Got: 1 +20.000 ns @ Got: 2 +30.000 ns @ Got: 3 +40.000 ns @ Got: 4 +50.000 ns @ Got: 5 +60.000 ns @ Got: 6 +70.000 ns @ Got: 7 +80.000 ns @ Got: 8 +90.000 ns @ Got: 9 +``` + +# Support + +If you need help using the PFPSim Framework, please +[send us an email at `pfpsim.help@gmail.com`](mailto:pfpsim.help@gmail.com) - we'd be happy to hear from you! + +If you think you've found a bug, or would like to request a new feature, +[please open an issue using github](https://github.com/pfpsim/PFPSim/issues) - we're always trying to improve PFPSim! + +# Contributing +If you'd like to contribute code back to PFPSim, please fork this github repository and send us a pull request! +Please make sure that your contribution passes our [continuous integration build](https://travis-ci.org/pfpsim/PFPSim). + +Any contribution to the C++ core code must respect the coding guidelines as stated on the wiki. We rely heavily on the +Google C++ Style Guide, with some differences listed in this repository's wiki. Our Travis builds include running +[`cpplint`](https://github.com/google/styleguide/tree/gh-pages/cpplint) to ensure correct style and formatting. + +# License + +This project is licensed under the GPLv2 - see [LICENSE](LICENSE) for details + +# Authors +Copyright (C) 2016 Concordia Univ., Montreal + - Samar Abdi + - Umair Aftab + - Gordon Bailey + - Faras Dewal + - Shafigh Parsazad + - Eric Tremblay + +Copyright (C) 2016 Ericsson +- Bochra Boughzala diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in new file mode 100644 index 0000000..a94a3fa --- /dev/null +++ b/cmake/cmake_uninstall.cmake.in @@ -0,0 +1,51 @@ +# +# PFPSim: Library for the Programmable Forwarding Plane Simulation Framework +# +# Copyright (C) 2016 Concordia Univ., Montreal +# Samar Abdi +# Umair Aftab +# Gordon Bailey +# Faras Dewal +# Shafigh Parsazad +# Eric Tremblay +# +# Copyright (C) 2016 Ericsson +# Bochra Boughzala +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + +if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") +endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif(NOT "${rm_retval}" STREQUAL 0) + else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") +endforeach(file) diff --git a/pfp-p4/.gitignore b/pfp-p4/.gitignore new file mode 100644 index 0000000..bbdf214 --- /dev/null +++ b/pfp-p4/.gitignore @@ -0,0 +1,10 @@ +p4-behavioral-model +CMakeFiles +CMakeCache.txt +Makefile +PFP-P4-prefix +cmake_install.cmake +libPFP-P4.a +pfp-p4/ +install_manifest.txt +cmake-configure diff --git a/pfp-p4/CMakeLists.txt b/pfp-p4/CMakeLists.txt new file mode 100644 index 0000000..cda2a1f --- /dev/null +++ b/pfp-p4/CMakeLists.txt @@ -0,0 +1,144 @@ +# +# PFPSim: Library for the Programmable Forwarding Plane Simulation Framework +# +# Copyright (C) 2016 Concordia Univ., Montreal +# Samar Abdi +# Umair Aftab +# Gordon Bailey +# Faras Dewal +# Shafigh Parsazad +# Eric Tremblay +# +# Copyright (C) 2016 Ericsson +# Bochra Boughzala +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + +cmake_minimum_required( VERSION 2.8.7) +PROJECT(PFP-P4)#TODO(LS):Check with samar about this thing. + +set(PFP_P4_MAJOR_VERSION 0) +set(PFP_P4_MINOR_VERSION 0) +set(PFP_P4_PATCH_VERSION 0) +set(PFP_P4_VERSION ${PFP_P4_MAJOR_VERSION}.${PFP_P4_MINOR_VERSION}.${PFP_P4_PATCH_VERSION}) + +#--------Set install paths (TODO(gb) factor out to toplevel +# Offer the user the choice of overriding the installation directories +set(INSTALL_LIB_DIR ${CMAKE_INSTALL_PREFIX}/lib CACHE PATH "Installation directory for libraries") +set(INSTALL_BIN_DIR bin CACHE PATH "Installation directory for executables") +set(INSTALL_INCLUDE_DIR ${CMAKE_INSTALL_PREFIX}/include CACHE PATH "Installation directory for header files") +set(DEF_INSTALL_CMAKE_DIR lib/cmake) +set(INSTALL_CMAKE_DIR ${DEF_INSTALL_CMAKE_DIR} CACHE PATH "Installation directory for CMake files") + +#-------------- Packaging CPACK -------------- +#TODO(LS): add for more packaging Src tarbals etc +SET(CPACK_GENERATOR "DEB") +SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Samar Abdi ") #required +SET(CPACK_PACKAGE_VERSION_MAJOR ${PFP_P4_MAJOR_VERSION}) +SET(CPACK_PACKAGE_VERSION_MINOR ${PFP_P4_MINOR_VERSION}) +SET(CPACK_PACKAGE_VERSION_PATCH ${PFP_P4_PATCH_VERSION}) +INCLUDE(CPack) + +#--------------Compiler Flags-------------------------------------------------- +include (CheckCXXCompilerFlag) +CHECK_CXX_COMPILER_FLAG ("-std=c++11" COMPILER_SUPPORTS_CXX11) +CHECK_CXX_COMPILER_FLAG ("-std=c++0x" COMPILER_SUPPORTS_CXX0X) +if(NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + if (COMPILER_SUPPORTS_CXX11) + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(CONF_CXX "clang") + elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(CONF_CXX "gcc") + endif() + else() + message(FATAL "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") + endif () +else() + message (FATAL "MSVC is not supported - Please use GCC or CLANG ") +endif () + +configure_file(cmake-configure-template "${CMAKE_CURRENT_SOURCE_DIR}/cmake-configure" @ONLY) + +include(ExternalProject) + +set(PFP_P4_LIB ${CMAKE_CURRENT_SOURCE_DIR}/libpfp-p4.a) +set(PFP_P4_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/pfp-p4) + +ExternalProject_Add(pfp-p4 # Name for custom target + #--Download step-------------- + # URL of git repo + GIT_REPOSITORY "https://github.com/pfpsim/p4-behavioral-model.git" + # Git branch name, commit id or tag + GIT_TAG "pfp" + # Should certificate for https be checked + + #--Update/Patch step---------- + UPDATE_COMMAND "" # Source work-tree update command + #--Configure step------------- + # Source dir to be used for build + SOURCE_DIR "p4-behavioral-model" + # Build tree configuration command + CONFIGURE_COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/cmake-configure" + #--Build step----------------- + # Command to drive the native build + BUILD_COMMAND "make" "-j2" # We don't know n of cores, but atleast we can -j2 + # Use source dir for build dir + BUILD_IN_SOURCE true + #--Install step--------------- + # Command to drive install after build + INSTALL_COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/cmake-extract" ${PFP_P4_LIB} ${PFP_P4_INCLUDE} + ##--Test step------------------ + # Add test step executed before install step + TEST_BEFORE_INSTALL false + # Add test step executed after install step + TEST_AFTER_INSTALL false +) + +install(FILES ${PFP_P4_LIB} + DESTINATION ${INSTALL_LIB_DIR} + COMPONENT pfp-p4) +install(DIRECTORY ${PFP_P4_INCLUDE} + DESTINATION ${INSTALL_INCLUDE_DIR}/pfpsim + COMPONENT pfp-p4) + +# Make relative paths absolute (needed later on) +foreach(p LIB BIN INCLUDE CMAKE) + set(var INSTALL_${p}_DIR) + if(NOT IS_ABSOLUTE "${${var}}") + set(${var} "${CMAKE_INSTALL_PREFIX}/${${var}}") + endif() +endforeach() + +file(RELATIVE_PATH REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}" "${INSTALL_INCLUDE_DIR}") + +set(CONF_INCLUDE_DIRS +"${INSTALL_CMAKE_DIR}/${REL_INCLUDE_DIR}") + +get_filename_component(PFP_P4_INCLUDE_FNAME ${PFP_P4_INCLUDE} NAME) +set(PFP_P4_INCLUDE_FULL_PATH ${INSTALL_INCLUDE_DIR}/pfpsim/${PFP_P4_INCLUDE_FNAME}) + +set(PFP_P4_CMAKE_CONFIG pfp-p4Config.cmake) +configure_file(${PFP_P4_CMAKE_CONFIG}.in "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PFP_P4_CMAKE_CONFIG}" @ONLY) + +set(PFP_P4_CMAKE_VERSION pfp-p4ConfigVersion.cmake) +configure_file(${PFP_P4_CMAKE_VERSION}.in ${PROJECT_BINARY_DIR}/${PFP_P4_CMAKE_VERSION} @ONLY) + +install(FILES + "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PFP_P4_CMAKE_CONFIG}" + ${PROJECT_BINARY_DIR}/${PFP_P4_CMAKE_VERSION} +DESTINATION ${INSTALL_CMAKE_DIR}/pfp-p4 +COMPONENT pfp-p4) diff --git a/pfp-p4/cmake-configure-template b/pfp-p4/cmake-configure-template new file mode 100755 index 0000000..cc76f9a --- /dev/null +++ b/pfp-p4/cmake-configure-template @@ -0,0 +1,24 @@ +#!/bin/bash + +# If Josh Kalderimis has approved this enviroment, then we must +# be running inside Travis! Quick, install P4 dependencies!! +if [[ "$HAS_JOSH_K_SEAL_OF_APPROVAL" == "true" ]] +then + ./install_deps.sh +fi + +extra_flags="" +set_cxx="" +if [[ "@CONF_CXX@" =~ .*clang.* ]] +then + extra_flags="-stdlib=libstdc++" + set_cxx="clang++" +elif [[ "@CONF_CXX@" =~ .*gcc.* ]]; then + # do nothing because P4 is hardcoding to g++ + set_cxx="g++" +fi + +./autogen.sh +# TODO(gordon) be smarter about `-O0 and -ggdb` +CXXFLAGS="$CXXFLAGS -DBMLOG_DEBUG_ON -DBMLOG_TRACE_ON -ggdb -O0 $extra_flags" CXX="$set_cxx" \ + ./configure --enable-debugger diff --git a/pfp-p4/cmake-extract b/pfp-p4/cmake-extract new file mode 100755 index 0000000..9dfe853 --- /dev/null +++ b/pfp-p4/cmake-extract @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# Make sure we have inputs +([ -z "$1" ] || [ -z "$2" ]) && echo "usage $0 " && exit 1 + +# These are the static libraries that we need +p4libs=(libbmi.a libbmsim.a libbflpmtrie.a) + +# And this one, which is a third party library +libs=libjson.a + +# These are the include directories we need +includes=(bf_lpm_trie bm_sim jsoncpp spdlog) + +libname=$1 +include_dir=$2 + +# Make the directory structure that we want +mkdir -p $include_dir + + +mri_script="create $libname" +# Function to find a library and copy it into a given folder +function add_lib_to_MRI_script { + mri_script="$mri_script\naddlib $(find . -name $1)" +} + +function cp_include { + cp -r $(find . -name $1 | grep --color=never include) $include_dir/ +} + +# Copy all the p4 libs to the P4 folder +for file in "${p4libs[@]}" +do + echo $file + add_lib_to_MRI_script $file +done + +# Copy the other (third-party) libs to the root lib folder +for file in $libs +do + echo $file + add_lib_to_MRI_script $file +done + +# Copy the includes +for file in "${includes[@]}" +do + echo $file + cp_include $file +done + +mri_script="$mri_script\nsave\nend\n" +echo -e $mri_script +echo -e $mri_script | ar -M diff --git a/pfp-p4/pfp-p4Config.cmake.in b/pfp-p4/pfp-p4Config.cmake.in new file mode 100644 index 0000000..e680944 --- /dev/null +++ b/pfp-p4/pfp-p4Config.cmake.in @@ -0,0 +1,43 @@ +# +# PFPSim: Library for the Programmable Forwarding Plane Simulation Framework +# +# Copyright (C) 2016 Concordia Univ., Montreal +# Samar Abdi +# Umair Aftab +# Gordon Bailey +# Faras Dewal +# Shafigh Parsazad +# Eric Tremblay +# +# Copyright (C) 2016 Ericsson +# Bochra Boughzala +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + +# - Config file for the PFP_P4 package +# It defines the following variables +# PFP_P4_INCLUDE_DIRS - include directories for PFP_P4 +# PFP_P4_LIBRARIES - libraries to link against +# calculate paths +find_library(LIBJUDY Judy REQUIRED) +find_library(LIBBOOST_SYSTEM boost_system REQUIRED) +find_library(LIBBOOST_PRGOPT boost_program_options REQUIRED) +find_library(LIBBOOST_THREAD boost_thread REQUIRED) +find_library(LIBBOOSTFILESYS boost_filesystem REQUIRED) + +set(PFP_P4_INCLUDE_DIRS "@CONF_INCLUDE_DIRS@/pfpsim/pfp-p4") +set(PFP_P4_LIBRARIES "pfp-p4 ${LIBJUDY} ${LIBBOOST_SYSTEM} ${LIBBOOST_PRGOPT} ${LIBBOOST_THREAD} ${LIBBOOSTFILESYS}") diff --git a/pfp-p4/pfp-p4ConfigVersion.cmake.in b/pfp-p4/pfp-p4ConfigVersion.cmake.in new file mode 100644 index 0000000..d34de87 --- /dev/null +++ b/pfp-p4/pfp-p4ConfigVersion.cmake.in @@ -0,0 +1,40 @@ +# +# PFPSim: Library for the Programmable Forwarding Plane Simulation Framework +# +# Copyright (C) 2016 Concordia Univ., Montreal +# Samar Abdi +# Umair Aftab +# Gordon Bailey +# Faras Dewal +# Shafigh Parsazad +# Eric Tremblay +# +# Copyright (C) 2016 Ericsson +# Bochra Boughzala +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + +set(PACKAGE_VERSION "@PFP_P4_VERSION@") +# Check whether the requested PACKAGE_FIND_VERSION is compatible +if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() diff --git a/pfpsim/CMakeLists.txt b/pfpsim/CMakeLists.txt new file mode 100644 index 0000000..4c01149 --- /dev/null +++ b/pfpsim/CMakeLists.txt @@ -0,0 +1,238 @@ +# +# PFPSim: Library for the Programmable Forwarding Plane Simulation Framework +# +# Copyright (C) 2016 Concordia Univ., Montreal +# Samar Abdi +# Umair Aftab +# Gordon Bailey +# Faras Dewal +# Shafigh Parsazad +# Eric Tremblay +# +# Copyright (C) 2016 Ericsson +# Bochra Boughzala +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + +cmake_minimum_required( VERSION 2.8.7) +PROJECT(PFPSIM)#TODO(LS):Check with samar about this thing. + +execute_process(COMMAND .travis/get_version_from_tag.sh major OUTPUT_VARIABLE PFPSIM_MAJOR_VERSION) +execute_process(COMMAND .travis/get_version_from_tag.sh minor OUTPUT_VARIABLE PFPSIM_MINOR_VERSION) +execute_process(COMMAND .travis/get_version_from_tag.sh patch OUTPUT_VARIABLE PFPSIM_PATCH_VERSION) +set(PFPSIM_VERSION ${PFPSIM_MAJOR_VERSION}.${PFPSIM_MINOR_VERSION}.${PFPSIM_PATCH_VERSION}) +message("PFPSim version is ${PFPSIM_VERSION}") + +ADD_SUBDIRECTORY(core) + +#--------Set install paths +# Offer the user the choice of overriding the installation directories +set(INSTALL_LIB_DIR ${CMAKE_INSTALL_PREFIX}/lib CACHE PATH "Installation directory for libraries") +set(INSTALL_BIN_DIR bin CACHE PATH "Installation directory for executables") +set(INSTALL_INCLUDE_DIR include CACHE PATH "Installation directory for header files") +set(DEF_INSTALL_CMAKE_DIR lib/cmake) +set(INSTALL_CMAKE_DIR ${DEF_INSTALL_CMAKE_DIR} CACHE PATH "Installation directory for CMake files") + +#-------------- Packaging CPACK -------------- +#TODO(LS): add for more packaging Src tarbals etc +SET(CPACK_GENERATOR "DEB") +SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Samar Abdi ") #required +SET(CPACK_PACKAGE_VERSION_MAJOR ${PFPSIM_MAJOR_VERSION}) +SET(CPACK_PACKAGE_VERSION_MINOR ${PFPSIM_MINOR_VERSION}) +SET(CPACK_PACKAGE_VERSION_PATCH ${PFPSIM_PATCH_VERSION}) +INCLUDE(CPack) + +#------------------------ Build +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libstdc++ -std=c++11 ") +else() + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ") +endif() + +#--------------Compiler Flags-------------------------------------------------- +include (CheckCXXCompilerFlag) +CHECK_CXX_COMPILER_FLAG ("-std=c++11" COMPILER_SUPPORTS_CXX11) +CHECK_CXX_COMPILER_FLAG ("-std=c++0x" COMPILER_SUPPORTS_CXX0X) +if(NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + if (COMPILER_SUPPORTS_CXX11) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -fmessage-length=0 -MMD -MP -Wno-reorder -Wno-return-type -Wno-unused -Wno-overloaded-virtual") + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libstdc++ -fcolor-diagnostics") + else() + message(FATAL "UNSUPPORTED COMPILER: ${CMAKE_CXX_COMPILER} --- USE GCC or CLANG") + endif() + else() + message(FATAL "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") + endif () +else() + message (FATAL "MSVC is not supported - Please use GCC or CLANG ") +endif () + +# PROTOBUF +include(FindProtobuf) +find_package(Protobuf REQUIRED) + +include_directories(${PROTOBUF_INCLUDE_DIR}) + +if(EXISTS ${PROTOBUF_PROTOC_EXECUTABLE}) + message(STATUS "Found PROTOBUF Compiler: ${PROTOBUF_PROTOC_EXECUTABLE}") +else() + message(FATAL_ERROR "Could not find PROTOBUF Compiler") +endif() + +# Generate protobuf files +set(PROTO_FILE_NAME "PFPSimDebugger") + +set(PROTO_SRC "${CMAKE_CURRENT_SOURCE_DIR}/core/proto/${PROTO_FILE_NAME}.pb.cc") +set(PROTO_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/core/proto/${PROTO_FILE_NAME}.pb.h") + +add_custom_command( + OUTPUT ${PROTO_SRC} + ${PROTO_HEADER} + COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} ARGS -I=${CMAKE_CURRENT_SOURCE_DIR}/core/proto --cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/core/proto ${CMAKE_CURRENT_SOURCE_DIR}/core/proto/${PROTO_FILE_NAME}.proto + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/core/proto/${PROTO_FILE_NAME}.proto" + COMMENT "Running C++ protocol buffer compiler" VERBATIM +) + +set_source_files_properties(${PROTO_SRC} ${PROTO_HEADER} PROPERTIES GENERATED TRUE) +set_source_files_properties(${CP_SRC} ${CP_HEADERS} PROPERTIES GENERATED TRUE) + +set_source_files_properties(${CP_FILES} PROPERTIES GENERATED TRUE) + +#--Find Packages +# Add find_package modules for SystemC and TLM +set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/core/cmake/modules/") +# Find SystemC and TLM +find_package (SystemC 2.3 REQUIRED) +find_package (TLM REQUIRED) +find_library (LIBNANOMSG nanomsg REQUIRED) +find_library (LIBPCAP pcap REQUIRED) + +include_directories (${SystemC_INCLUDE_DIRS} ${TLM_INCLUDE_DIRS}) +include_directories(${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) + +# Support for SystemC dynamic processes +add_definitions (-DSC_INCLUDE_DYNAMIC_PROCESSES) + +#---------------------- +set(PFPSIM_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/pfpsim.h) + +set(PFPSIM_LIBRARY + ${PFPSIM_LIBRARY} + ${PFPSIM_HEADER} + ${PROTO_SRC} + ${PROTO_HEADER} + ${PROTOBUF_LIBRARY} + ${CP_FILES} + ${SystemC_LIBRARIES}) + +add_library(pfpsim STATIC ${PFPSIM_LIBRARY}) +add_dependencies(pfpsim cp) +get_property(PFPSIM_INC_DIR TARGET pfpsim PROPERTY INCLUDE_DIRECTORIES) +set_target_properties(pfpsim PROPERTIES +PUBLIC_HEADER "${PFPSIM_INSTALL_FILES}") + +set(INSTALL_PFPSIM_DIR "${INSTALL_INCLUDE_DIR}/pfpsim/pfpsim") +set(INSTALL_PFPSIM_CORE_DIR "${INSTALL_PFPSIM_DIR}/core") + +install(TARGETS pfpsim + # IMP: Add the PFPSIM library to the "export-set" + EXPORT pfpsimTargets + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin + LIBRARY DESTINATION "${INSTALL_LIB_DIR}" COMPONENT shlib + ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" COMPONENT ${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/lib + PUBLIC_HEADER DESTINATION "${INSTALL_PFPSIM_CORE_DIR}" + COMPONENT core) + +install(FILES ${PFPSIM_HEADER} + DESTINATION ${INSTALL_PFPSIM_DIR} + COMPONENT pfpsim) + +# Install generated proto header +install(FILES ${PROTO_HEADER} + DESTINATION "${INSTALL_PFPSIM_CORE_DIR}/proto" + COMPONENT core) + +# Install debugger headers +install(FILES ${DEBUGGER_HEADERS} + DESTINATION "${INSTALL_PFPSIM_CORE_DIR}/debugger/" + COMPONENT core) + +# Install cp headers +install(FILES ${CP_HEADERS} + DESTINATION "${INSTALL_PFPSIM_CORE_DIR}/cp/" + COMPONENT core) + + #-------------- CMake Install Steps + + # Make relative paths absolute (needed later on) + foreach(p LIB BIN INCLUDE CMAKE) + set(var INSTALL_${p}_DIR) + if(NOT IS_ABSOLUTE "${${var}}") + set(${var} "${CMAKE_INSTALL_PREFIX}/${${var}}") + endif() + endforeach() + #------- + # Add all targets to the build-tree export set + export(TARGETS pfpsim FILE "${PROJECT_BINARY_DIR}/pfpsimTargets.cmake") + # Create the PFPSIMConfig.cmake and PFPSIMConfigVersion files + file(RELATIVE_PATH REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}/pfpsim" "${INSTALL_INCLUDE_DIR}") + + # install build-dir + set(CONF_INCLUDE_DIRS + "${PROJECT_SOURCE_DIR}" + "${PROJECT_BINARY_DIR}" + ) + set(CONF_EXPORT_TARGETS + "pfpsim" + ) + + configure_file(pfpsimConfig.cmake.in "${PROJECT_BINARY_DIR}/pfpsimConfig.cmake" @ONLY) + # install tree + set(CONF_INCLUDE_DIRS + "\${PFPSIM_CMAKE_DIR}/${REL_INCLUDE_DIR}" + + ) + configure_file(pfpsimConfig.cmake.in "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/pfpsimConfig.cmake" @ONLY) + # both + configure_file(pfpsimConfigVersion.cmake.in "${PROJECT_BINARY_DIR}/pfpsimConfigVersion.cmake" @ONLY) + # Install PFPSIMConfig.cmake & PFPSIMConfigVersion.cmake & FindSystemC/TLM + install(FILES + "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/pfpsimConfig.cmake" + "${PROJECT_BINARY_DIR}/pfpsimConfigVersion.cmake" + "${PROJECT_SOURCE_DIR}/core/cmake/modules/FindSystemC.cmake" + "${PROJECT_SOURCE_DIR}/core/cmake/modules/FindTLM.cmake" + DESTINATION "${INSTALL_CMAKE_DIR}/pfpsim" + COMPONENT core) + # Install the export set for use with the install tree/thing + install(EXPORT pfpsimTargets DESTINATION + "${INSTALL_CMAKE_DIR}/pfpsim" + COMPONENT core) + +#--------Doxygen---------------- +# add a target [doc] to generate API documentation with Doxygen +find_package(Doxygen) +if(DOXYGEN_FOUND) + + set(doxyfile_in ${CMAKE_CURRENT_SOURCE_DIR}/doxygen/Doxyfile) + add_custom_target(doc + COMMAND doxygen ${doxyfile_in} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/doxygen + COMMENT "Generating API documentation with Doxygen" + VERBATIM) + +endif(DOXYGEN_FOUND) diff --git a/pfpsim/core/CMakeLists.txt b/pfpsim/core/CMakeLists.txt new file mode 100644 index 0000000..b099444 --- /dev/null +++ b/pfpsim/core/CMakeLists.txt @@ -0,0 +1,83 @@ +# +# PFPSim: Library for the Programmable Forwarding Plane Simulation Framework +# +# Copyright (C) 2016 Concordia Univ., Montreal +# Samar Abdi +# Umair Aftab +# Gordon Bailey +# Faras Dewal +# Shafigh Parsazad +# Eric Tremblay +# +# Copyright (C) 2016 Ericsson +# Bochra Boughzala +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + + + +configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY) + +ADD_SUBDIRECTORY(debugger "${CMAKE_CURRENT_BINARY_DIR}/debugger") +ADD_SUBDIRECTORY(cp) + +set(DEBUGGER_HEADERS ${DEBUGGER_HEADERS} PARENT_SCOPE) +set(CP_HEADERS ${CP_HEADERS} PARENT_SCOPE) + +set(PFPSIMCoreSources +${CMAKE_CURRENT_SOURCE_DIR}/PFPObject.cpp +${CMAKE_CURRENT_SOURCE_DIR}/StringUtils.cpp +${CMAKE_CURRENT_SOURCE_DIR}/PFPConfig.cpp +${CMAKE_CURRENT_SOURCE_DIR}/PFPContext.cpp +${CMAKE_CURRENT_SOURCE_DIR}/ConfigurationParameters.cpp +${CMAKE_CURRENT_SOURCE_DIR}/DebuggerUtilities.cpp +${CMAKE_CURRENT_SOURCE_DIR}/pfp_main.cpp +${DEBUGGER_SRC} +) + +set(PFPSIM_INCLUDE_HEADERS +${CMAKE_CURRENT_SOURCE_DIR}/PFPObject.h +${CMAKE_CURRENT_SOURCE_DIR}/PFPConfig.h +${CMAKE_CURRENT_SOURCE_DIR}/ConfigurationParameters.h +${CMAKE_CURRENT_SOURCE_DIR}/TrType.h +${CMAKE_CURRENT_SOURCE_DIR}/MTQueue.h +${CMAKE_CURRENT_SOURCE_DIR}/LMTQueue.h +${CMAKE_CURRENT_SOURCE_DIR}/promptcolors.h +${CMAKE_CURRENT_SOURCE_DIR}/PacketBase.h +${CMAKE_CURRENT_SOURCE_DIR}/PFPObserver.h +${CMAKE_CURRENT_SOURCE_DIR}/StringUtils.h +${CMAKE_CURRENT_SOURCE_DIR}/DebuggerUtilities.h +${CMAKE_CURRENT_SOURCE_DIR}/pfp_main.h +) + +set(PFPSIM_LIBRARY + ${PFPSIMCoreSources} + ${PFPSIM_INCLUDE_HEADERS} + ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in + PARENT_SCOPE) + +set(CP_FILES + ${CP_SRC} + ${CP_HEADERS} + PARENT_SCOPE) + +set(PFPSIM_INSTALL_FILES + ${PFPSIM_INCLUDE_HEADERS} + ${CMAKE_CURRENT_SOURCE_DIR}/json.hpp + ${CMAKE_CURRENT_BINARY_DIR}/config.h + PARENT_SCOPE + ) diff --git a/pfpsim/core/ConfigurationParameters.cpp b/pfpsim/core/ConfigurationParameters.cpp new file mode 100644 index 0000000..c2406eb --- /dev/null +++ b/pfpsim/core/ConfigurationParameters.cpp @@ -0,0 +1,279 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "ConfigurationParameters.h" +#include +#include "PFPObject.h" +#include "promptcolors.h" + +namespace pfp { +namespace core { + +ConfigurationParameterNode::ConfigurationParameterNode(std::string filename) { + std::ifstream ifs(filename); + if (!ifs.fail()) { + std::string content((std::istreambuf_iterator(ifs)), + (std::istreambuf_iterator())); + ConfigurationParameters = json::parse(content); + } else { + std::cerr << On_Purple << "Could not open file: " << txtrst << On_Red + << filename << txtrst << std::endl; + exit(-1); + } +} + +ConfigurationParameterNode::ConfigurationParameterNode( + ConfigurationParameterNode::Matrix object) + :ConfigurationParameters(object) { +} + +ConfigurationParameterNode::ConfigurationParameterNode() + :ConfigurationParameters(nullptr) { +} + +ConfigurationParameterNode::Matrix +ConfigurationParameterNode::Searchinparent(std::string param, + pfp::core::PFPObject* CurrentModule) { + std::string name = CurrentModule->module_name(); + std::size_t found = name.find("top"); + // Current Module is not Top, its parent might be top + if (found == std::string::npos) { + auto result = CurrentModule->SimulationParameters.LocalSearch(param); + // Do a local search if it fails then call its parent. + if (result == nullptr) { + return CurrentModule->SimulationParameters.Searchinparent(param, + CurrentModule->GetParent()); + } else { + return result; + } + } else { // We have reached the top module of the hierarchy + auto result = CurrentModule->SimulationParameters.LocalSearch(param); + if (result == nullptr) { + std::cerr << "Parameter: " << param + << " not found in Hierarchal Search. " + << "Please check configuration files" << endl; + exit(USER_ERROR_FATAL); + } else { + return result; + } + } +} + +ConfigurationParameterNode::Matrix +ConfigurationParameterNode::Searchinparent(int position, + pfp::core::PFPObject* CurrentModule) { + std::string name = CurrentModule->module_name(); + std::size_t found = name.find("top"); + // Current Module is not Top, its parent might be top + if (found == std::string::npos) { + auto result = CurrentModule->SimulationParameters.LocalSearch(position); + // Do a local search if it fails then call its parent. + if (result == nullptr) { + return CurrentModule->SimulationParameters.Searchinparent(position, + CurrentModule->GetParent()); + } else { + return result; + } + } else { // We have reached the top module of the hierarchy + auto result = CurrentModule->SimulationParameters.LocalSearch(position); + if (result == nullptr) { + std::cerr << "Array Lookup: " << position + << " not found in HierarchalSearch. " + << "Please check configuration files" << endl; + exit(USER_ERROR_FATAL); + } else { + return result; + } + } +} + +ConfigurationParameterNode::Matrix +ConfigurationParameterNode::HeirarchalSearch(std::string param) { + for (json::iterator it = ConfigurationParameters.begin(); + it != ConfigurationParameters.end(); ++it) { + if (it.key() == param) { + return it.value(); + } + } + + // No Match go to a parent + sc_object* current_scmodule + = sc_get_current_process_handle().get_parent_object(); + if (dynamic_cast(current_scmodule)) { + return Searchinparent(param, + dynamic_cast(current_scmodule)->GetParent()); + } else { + std::cerr << On_Red << "[PFPSIM runtime error]: " << txtrst + << "Not in a PFPSIM module heirarichal search for param: " + << param << " failed" << endl; + exit(0); + } +} + +ConfigurationParameterNode::Matrix +ConfigurationParameterNode::HeirarchalSearch(int position) { + if (position < ConfigurationParameters.size()) { + return ConfigurationParameters.at(position); + } else { + sc_object* current_scmodule + = sc_get_current_process_handle().get_parent_object(); + if (dynamic_cast(current_scmodule)) { + return Searchinparent(position, + dynamic_cast(current_scmodule)); + } else { + std::cerr << On_Red << "[PFPSIM runtime error]: " << txtrst + << "Not in a PFPSIM module heirarichal search for param: " + << position << " failed" << endl; + exit(0); + } + } +} + +ConfigurationParameterNode::Matrix +ConfigurationParameterNode::LocalSearch(std::string param) { + for (json::iterator it = ConfigurationParameters.begin(); + it != ConfigurationParameters.end(); ++it) { + if (it.key() == param) { + return it.value(); + } + } + return nullptr; // Search Failed return a nullptr +} + +ConfigurationParameterNode::Matrix +ConfigurationParameterNode::LocalSearch(int position) { + if (position < ConfigurationParameters.size()) { + return ConfigurationParameters.at(position); + } else { + return nullptr; // Search failed return a nullptr + } +} + +ConfigurationParameterNode::Matrix ConfigurationParameterNode::get() { + return ConfigurationParameters; +} + + +ConfigurationParameterNode::CompareResult +ConfigurationParameterNode::CompareStructure( + ConfigurationParameterNode::Matrix compare, + ConfigurationParameterNode::Matrix golden) { + // cout<<" ----Comapring:"< +#include +#include +#include "json.hpp" +using json = nlohmann::json; + +namespace pfp{ +namespace core{ + +class PFPObject; +class ConfigurationParameterNode { + public: + typedef json Matrix; // Dom representation is in json format using json lib. + + explicit ConfigurationParameterNode(std::string filename); + ConfigurationParameterNode(Matrix object); // NOLINT(runtime/explicit) + ConfigurationParameterNode(); + + /** + * [operator[] overloads for searching and chaining to maintain intuitive syntax Default behaviour is for hierarchy lookups] + * @param position [index position in list ] + * @return [description] + */ + ConfigurationParameterNode operator[] (const int position) { + // std::cout << "Operator["< + T get() { + return ConfigurationParameters.get(); + } + + Matrix LocalSearch(std::string param); + Matrix LocalSearch(int position); + Matrix HeirarchalSearch(std::string param); + Matrix HeirarchalSearch(int position); + Matrix Searchinparent(std::string Value, pfp::core::PFPObject* ParentModule); + Matrix Searchinparent(int position, pfp::core::PFPObject* ParentModule); + + + struct CompareResult{ + bool result; + Matrix resultobject; + }; + + CompareResult CompareStructure(Matrix compareto, Matrix golden); + ConfigurationParameterNode MatchandFill( + ConfigurationParameterNode matchagainst); + void raiseError(std::string message, Matrix compare, Matrix golden); + void raiseError(std::string); + + private: + /*Variable that stores the JSON/XML DOM object*/ + Matrix ConfigurationParameters; +}; +}; // namespace core +}; // namespace pfp + +#endif // CORE_CONFIGURATIONPARAMETERS_H_ diff --git a/pfpsim/core/DebuggerUtilities.cpp b/pfpsim/core/DebuggerUtilities.cpp new file mode 100644 index 0000000..bdbeff4 --- /dev/null +++ b/pfpsim/core/DebuggerUtilities.cpp @@ -0,0 +1,49 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "DebuggerUtilities.h" + +DebuggerUtilities::DebuggerUtilities() { + /* instantiate DebugObserver */ + debug_obs = std::make_shared(); +} + +void DebuggerUtilities::notify_observers(pfp::core::PFPObject* object) { + while (1) { + auto func = object->events_.pop(); + func(); + } +} + +void +DebuggerUtilities::registerControlPlaneToDebugger( + pfp::core::db::CPDebuggerInterface *cp_debug_if) { + debug_obs->registerCP(cp_debug_if); +} diff --git a/pfpsim/core/DebuggerUtilities.h b/pfpsim/core/DebuggerUtilities.h new file mode 100644 index 0000000..9fec575 --- /dev/null +++ b/pfpsim/core/DebuggerUtilities.h @@ -0,0 +1,58 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef CORE_DEBUGGERUTILITIES_H_ +#define CORE_DEBUGGERUTILITIES_H_ + +#include "debugger/CPDebuggerInterface.h" +#include "debugger/DebugObserver.h" +#include "PFPObject.h" + +class DebuggerUtilities { + public: + DebuggerUtilities(); + virtual ~DebuggerUtilities() = default; + + /** + * Register ControlPlane Instance to the debugger object + * @param cp_debug_if + */ + void registerControlPlaneToDebugger( + pfp::core::db::CPDebuggerInterface *cp_debug_if); + /** + * Thread function to service notifications from all observers + * @param object which launches the thread [top] + */ + void notify_observers(pfp::core::PFPObject* object); + //! Observer for the debugger + std::shared_ptr debug_obs; +}; + +#endif // CORE_DEBUGGERUTILITIES_H_ diff --git a/pfpsim/core/LMTQueue.h b/pfpsim/core/LMTQueue.h new file mode 100644 index 0000000..86af554 --- /dev/null +++ b/pfpsim/core/LMTQueue.h @@ -0,0 +1,141 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/* + * LMTQueue.h + * + * Created on: Oct 13, 2014 + * Author: Kamil Saigol + */ +/** + * @class LMTQueue + * A size-limited multiple-producer, multiple-consumer, thread-safe queue implemented using SystemC primitives + * The LMTQueue is used as the input buffer in the NPU modules + */ +#ifndef CORE_LMTQUEUE_H_ +#define CORE_LMTQUEUE_H_ + +#include +#include "systemc.h" + +template +class LMTQueue { + public: + /** + * Construct a LMTQueue + */ + LMTQueue() + : queue_(), mutex_(sc_gen_unique_name("mutex_")), + cond_not_empty_(sc_gen_unique_name("cond_not_empty_")), slots_(0) { + } + /** + * Pop the top element from the LMTQueue and return a copy + * @return Copy of the top element + */ + T pop() { + while (queue_.empty()) { + wait(cond_not_empty_); + } + mutex_.lock(); + auto item = queue_.front(); + queue_.pop(); + slots_--; + mutex_.unlock(); + cond_not_full_.notify(); + return item; + } + + /** + * Pop the top element from the LMTQueue and copy it into the output argument + * @param Output argument + */ + void pop(T& item) { + while (queue_.empty()) { + wait(cond_not_empty_); + } + mutex_.lock(); + item = queue_.front(); + queue_.pop(); + slots_--; + mutex_.unlock(); + cond_not_full_.notify(); + } + + /** + * Push an item onto the LMTQueue + * @param Item to push + */ + void push(const T& item) { + while (this->full()) { + wait(cond_not_full_); + } + mutex_.lock(); + queue_.push(item); + slots_++; + mutex_.unlock(); + cond_not_empty_.notify(); + } + + /** + * Push an item onto the LMTQueue (explicit move) + * @param Item to move + */ + void push(T&& item) { + while (this->full()) { + wait(cond_not_full_); + } + mutex_.lock(); + queue_.push(std::move(item)); + slots_++; + mutex_.unlock(); + cond_not_empty_.notify(); + } + + size_t available() const { + return N - slots_; + } + + bool full() const { + return (slots_ == N); + } + + bool empty() const { + return queue_.empty(); + } + + private: + std::queue queue_; /*!< Internal queue */ + sc_mutex mutex_; /*!< Guard */ + //! Events to notify when not empty and not full + sc_event cond_not_empty_, cond_not_full_; + size_t slots_; /*!< Number of queue positions used */ +}; + +#endif // CORE_LMTQUEUE_H_ diff --git a/pfpsim/core/MTQueue.h b/pfpsim/core/MTQueue.h new file mode 100644 index 0000000..1b14718 --- /dev/null +++ b/pfpsim/core/MTQueue.h @@ -0,0 +1,133 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/* + * MTQueue.h + * + * Created on: Oct 13, 2014 + * Author: Kamil Saigol + */ +/** + * @class MTQueue + * A multiple-producer, multiple-consumer, thread-safe queue implemented using SystemC primitives + * The MTQueue is used as the input buffer in the NPU modules + */ +#ifndef CORE_MTQUEUE_H_ +#define CORE_MTQUEUE_H_ + +#include +#include "systemc.h" + +template +class MTQueue { + public: + /** + * Construct a MTQueue + */ + MTQueue() : /*sem_(1),*/ queue_(), + mutex_(sc_gen_unique_name("mutex_")), + cond_(sc_gen_unique_name("cond_")) { + } + /** + * Pop the top element from the MTQueue and return a copy + * @return Copy of the top element + */ + T pop() { + while (queue_.empty()) { + wait(cond_); + } + mutex_.lock(); + // sem_.wait(); + auto item = queue_.front(); + queue_.pop(); + mutex_.unlock(); + // sem_.post(); + return item; + } + + /** + * Pop the top element from the MTQueue and copy it into the output argument + * @param Output argument + */ + void pop(T& item) { + while (queue_.empty()) { + wait(cond_); + } + mutex_.lock(); + // sem_.wait(); + item = queue_.front(); + queue_.pop(); + mutex_.unlock(); + // sem_.post(); + } + + /** + * Push an item onto the MTQueue + * @param Item to push + */ + void push(const T& item) { + mutex_.lock(); + // sem_.wait(); + queue_.push(item); + // sem_.post(); + mutex_.unlock(); + cond_.notify(); + } + + /** + * Push an item onto the MTQueue (explicit move) + * @param Item to move + */ + void push(T&& item) { + mutex_.lock(); + // sem_.wait(); + queue_.push(std::move(item)); + // sem_.post(); + mutex_.unlock(); + cond_.notify(); + } + /* + * Return Size of Queue + * @param none + */ + void size(int& qsize) { + mutex_.lock(); + qsize = queue_.size(); + mutex_.unlock(); + } + + private: + std::queue queue_; /*!< Internal queue */ + sc_mutex mutex_; /*!< Guard */ + sc_event cond_; /*!< Event to notify when not empty */ + // sc_semaphore sem_; +}; + +#endif // CORE_MTQUEUE_H_ diff --git a/pfpsim/core/PFPConfig.cpp b/pfpsim/core/PFPConfig.cpp new file mode 100644 index 0000000..65e3379 --- /dev/null +++ b/pfpsim/core/PFPConfig.cpp @@ -0,0 +1,146 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * PFPConfig.cpp + * + * Created on: Oct 19, 2014 + * Author: Kamil + */ + +// #define cores_8 + +// #include "Configuration.h" +#include "PFPConfig.h" +#include +#include + +namespace pfp { +namespace core { + +PFPConfig::PFPConfig() { + if (verbositylevels.size() == verbosity::PROFILE_LEVELS) { + for (int i = 0; i < verbositylevels.size(); i++) { + verbosity_levels_map.emplace(verbositylevels[i], i); + } + } else { + std::cerr << " Verbosity Levels declaration error @" + << VERBOSITY_LEVELS_FILE_MARKER << ":" + << VERBOSITY_LEVELS_LINE_MARKER << std::endl; + std::cerr << " Levels declared in enum : " + << verbosity::PROFILE_LEVELS << std::endl; + std::cerr << " Levels declared in vector : " + << verbositylevels.size() << std::endl; + exit(USER_ERROR_FATAL); + } +} + +PFPConfig& PFPConfig::get() { + static PFPConfig instance; + return instance; +} + +const PFPConfig::verbosity PFPConfig::get_verbose_level() { + return verbo; +} +void PFPConfig::set_verbose_level(PFPConfig::verbosity level) { + verbo = level; +} +void PFPConfig::set_verbose_level(std::string verbosity_level) { + auto search = verbosity_levels_map.find(verbosity_level); + if (search != verbosity_levels_map.end()) { + verbo = static_cast(search->second); + } else { + std::cerr << "Error: Setting Verbose Level: " << verbosity_level + << " is invalid" << endl; + std::cerr << "Valid verbose levels are: "; + for (auto level : verbositylevels) { + std::cerr << level << ", "; + } + std::cerr << endl; + std::cerr << " Verbosity Levels declared @" << VERBOSITY_LEVELS_FILE_MARKER + << ":" << VERBOSITY_LEVELS_LINE_MARKER << endl; + exit(USER_ERROR_FATAL); + } +} + +void +PFPConfig::set_command_line_arg_vector(std::vector & args) { + for (auto & arg : args) { + auto equals = arg.find('='); + // No equals sign, that's a paddlin' + // https://www.youtube.com/watch?v=ZXQR-cPXlmY + if (equals == std::string::npos) { + std::stringstream err; + err << "Invalid key-value pair syntax " << arg + << " in command line args"; + throw std::runtime_error(err.str()); + } + + auto key = arg.substr(0, equals); + auto val = arg.substr(equals + 1); + + argMap[key] = val; + } +} + +std::string & PFPConfig::get_command_line_arg(std::string key) { + auto it = argMap.find(key); + + if (it == argMap.end()) { + std::stringstream err; + err << "Missing required command line arg " << key; + throw std::runtime_error(err.str()); + } else { + return it->second; + } +} + +void PFPConfig::SetConfigFilePath(std::string path) { + ConfigFilePath = path; +} +std::string PFPConfig::getConfigFilePath()const { + return ConfigFilePath; +} +void PFPConfig::SetOutputDirPath(std::string path) { + OutputDirPath = path; +} +std::string PFPConfig::getOutputDirPath()const { + return OutputDirPath; +} +void PFPConfig::set_debugger_flag(bool flag_value) { + debugger_flag = flag_value; +} +bool PFPConfig::debugger_flag_status() { + return debugger_flag; +} + +}; // namespace core +}; // namespace pfp diff --git a/pfpsim/core/PFPConfig.h b/pfpsim/core/PFPConfig.h new file mode 100644 index 0000000..49b0521 --- /dev/null +++ b/pfpsim/core/PFPConfig.h @@ -0,0 +1,151 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * PFPConfig.h + * + * Created on: Oct 18, 2014 + * Author: Kamil + */ +/** + * @class PFPConfig + * WARNING: This is a singleton + * It might be better to just change this to a global variable + * Needs a compiler that is highly compliant with C++11 + */ +#ifndef CORE_PFPCONFIG_H_ +#define CORE_PFPCONFIG_H_ + +#include +#include +#include +#include +#include "systemc.h" + +#define INPUT_FILE_ERROR -7 +#define USER_ERROR_FATAL -10 +#define SPCONFIG(param) \ +pfp::core::GetParameter(param); +#define SPSETCONFIGPATH(path) \ +pfp::core::PFPConfig::get().SetConfigFilePath(path); +#define SPSETOUTPUTDIRPATH(path) \ +pfp::core::PFPConfig::get().SetOutputDirPath(path); +#define SPSETARGS(args) \ +pfp::core::PFPConfig::get().set_command_line_arg_vector(args); +#define SPARG(key) \ +pfp::core::PFPConfig::get().get_command_line_arg(key) +#define CONFIGROOT \ +pfp::core::PFPConfig::get().getConfigFilePath() +#define OUTPUTDIR \ +pfp::core::PFPConfig::get().getOutputDirPath() +#define ISVERBOSITY(level) \ +pfp::core::PFPConfig::get().get_verbose_level() \ + == pfp::core::PFPConfig::get().verbosity::level +#define SET_PFP_DEBUGGER_FLAG(debugger_enabled) \ +pfp::core::PFPConfig::get().set_debugger_flag(debugger_enabled) +#define PFP_DEBUGGER_ENABLED \ +pfp::core::PFPConfig::get().debugger_flag_status() + +namespace pfp { +namespace core { + +class PFPConfig { + public: + /** + * Get a reference to the configuration class + * @return Singleton reference to configuration + */ + static PFPConfig& get(); + + void SetConfigFilePath(std::string path); + std::string getConfigFilePath() const; + void SetOutputDirPath(std::string path); + std::string getOutputDirPath() const; + + #define VERBOSITY_LEVELS_LINE_MARKER __LINE__ + #define VERBOSITY_LEVELS_FILE_MARKER __FILE__ + enum verbosity { + normal, + minimal, + p4profile, + profile, + debugger, + debug, + PROFILE_LEVELS = debug + 1 /*returns the number of enums in verbosity*/ + }; + /* + * Warning: + * enum is implicitly tied to vector, index positions of levels in both containers should be same + * so that it returns the same enum value from the map + */ + std::vector verbositylevels { + "normal", "minimal", "p4profile", "profile", "debugger", "debug"}; + + /** + * Pop the currently running parameter values + * PFPConfig only allows the NPU to call simulation complete + * TODO: Once implemented, change this so that a simulation controller calls simulation complete + */ + const verbosity get_verbose_level(); + void set_verbose_level(PFPConfig::verbosity); + void set_verbose_level(std::string verbosity_level); + + void set_command_line_arg_vector(std::vector & v); + std::string & get_command_line_arg(std::string key); + void set_debugger_flag(bool flag_value); + bool debugger_flag_status(); + + + private: + /** + * Private constructor + */ + PFPConfig(); + + PFPConfig(const PFPConfig &) = delete; + PFPConfig& operator=(const PFPConfig &) = delete; + PFPConfig(PFPConfig &&) = delete; + PFPConfig& operator=(PFPConfig &&) = delete; + + private: + std::string verbose; + verbosity verbo; + sc_mutex pop_guard; + std::string ConfigFilePath; + std::string OutputDirPath; + std::map configMap; + std::map argMap; + /* Map that returns the enum from the string*/ + std::map verbosity_levels_map; + bool debugger_flag; +}; +}; // namespace core +}; // namespace pfp +#endif // CORE_PFPCONFIG_H_ diff --git a/pfpsim/core/PFPContext.cpp b/pfpsim/core/PFPContext.cpp new file mode 100644 index 0000000..fcf3aac --- /dev/null +++ b/pfpsim/core/PFPContext.cpp @@ -0,0 +1,58 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "PFPObject.h" +#include "PFPContext.h" +#include + +// Generated by `pfpgen` +extern std::unique_ptr create_top(); + +namespace pfp { +namespace core { + +void PFPContext::ensure_top_initialized() { + if (!top_instance) { + top_instance = create_top(); + } +} + +PFPContext & PFPContext::get_current_context() { + if (!instance) { + instance.reset(new PFPContext()); + } + + return *instance; +} + +std::unique_ptr PFPContext::instance{nullptr}; + +} // namespace core +} // namespace pfp diff --git a/pfpsim/core/PFPContext.h b/pfpsim/core/PFPContext.h new file mode 100644 index 0000000..650b7bb --- /dev/null +++ b/pfpsim/core/PFPContext.h @@ -0,0 +1,55 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef CORE_PFPCONTEXT_H_ +#define CORE_PFPCONTEXT_H_ + +#include "PFPObject.h" + +namespace pfp { +namespace core { + +class PFPContext { + public: + void ensure_top_initialized(); + + static PFPContext & get_current_context(); + + private: + PFPContext() = default; + + std::unique_ptr top_instance{nullptr}; + static std::unique_ptr instance; +}; + +} // namespace core +} // namespace pfp + +#endif // CORE_PFPCONTEXT_H_ diff --git a/pfpsim/core/PFPObject.cpp b/pfpsim/core/PFPObject.cpp new file mode 100644 index 0000000..9a31264 --- /dev/null +++ b/pfpsim/core/PFPObject.cpp @@ -0,0 +1,328 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "PFPObject.h" +#include +#include + +#define SIM_ERROR_FATAL -1337 + +namespace pfp { +namespace core { + +MTQueue> PFPObject::events_; + +PFPObject::PFPObject(const std::string& module_name, + std::string BaseConfigFile, std::string InstanceConfigFile, + PFPObject* parent, bool enable_dicp): + GlobalConfigPath(CONFIGROOT), + dicp_enabled(enable_dicp), + module_name_(module_name), + parent_(parent) { +// cout<GetParent()); + } else { + return result; + } + // return SimulationParameters[param].get(); +} + +pfp::core::ConfigurationParameterNode +PFPObject::GetParameterfromParent(std::string param, PFPObject* parent) { + auto result = parent->SimulationParameters.LocalSearch(param); + if (parent->module_name() !="top") { + if (result == nullptr) { + return GetParameterfromParent(param, parent->GetParent()); + } else { + return result; + } + } else if (parent->module_name() == "top") { + if (result == nullptr) { + std::cerr << "Parameter Heirarchy Search Failed: " + << parent->module_name() << " for: " << param << std::endl; + exit(0); + } else { + return result; + } + } +} + +const std::string& PFPObject::module_name() const { + return module_name_; +} + +PFPObject* PFPObject::GetParent() { + return parent_; +} + + +std::vector PFPObject::ModuleHierarchy() { + std::vector hierarchy; + hierarchy.push_back(module_name()); + std::string name = module_name(); + std::size_t found = name.find("top"); + if (found == std::string::npos) { // Current Module is not Top + std::vector result_p = parent_->ModuleHierarchy(); + std::vector result; + result.reserve(hierarchy.size()+result_p.size()); + result.insert(result.end(), hierarchy.begin(), hierarchy.end()); + result.insert(result.end(), result_p.begin(), result_p.end()); + return result; + } else { // TOP + std::vector result1; + result1.push_back(module_name()); + return result1; + } +} + +//========================================// +// Observers // +//========================================// + +bool PFPObject::add_counter(const std::string& counter_name, + std::size_t counter_value) { + if (counters_.find(counter_name) == counters_.end()) { + counters_.emplace(counter_name, counter_value); + notify_counter_added(counter_name, sc_time_stamp().to_default_time_units()); + return true; + } else { + return false; + } +} + +bool PFPObject::set_counter(const std::string& counter_name, + std::size_t counter_value) { + try { + if (num_counters() == 0) { + return false; + } else { + counters_.at(counter_name) = counter_value; + notify_counter_changed(counter_name, counter_value, + sc_time_stamp().to_default_time_units()); + return true; + } + } + catch(std::out_of_range& e) { + return false; + } +} + +bool PFPObject::remove_counter(const std::string& counter_name) { + try { + if (num_counters() == 0) { + return false; + } else { + counters_.erase(counter_name); + notify_counter_removed(counter_name, + sc_time_stamp().to_default_time_units()); + return true; + } + } + catch(std::out_of_range &e) { + return false; + } +} + +std::size_t PFPObject::counter_value(const std::string& counter_name) const { + return counters_.at(counter_name); +} + +bool PFPObject::increment_counter(const std::string& counter_name) { + try { + ++counters_.at(counter_name); + notify_counter_changed(counter_name, counters_.at(counter_name), + sc_time_stamp().to_default_time_units()); + return true; + } + catch(std::out_of_range &e) { + return false; + } +} + +bool PFPObject::increment_counter(const std::string& counter_name, + const int incr_amount) { + try { + counters_.at(counter_name) += incr_amount; + notify_counter_changed(counter_name, counters_.at(counter_name), + sc_time_stamp().to_default_time_units()); + return true; + } + catch(std::out_of_range &e) { + return false; + } +} + +bool PFPObject::decrement_counter(const std::string& counter_name) { + try { + if (counters_.at(counter_name) == 0) { + return false; + } else { + --counters_.at(counter_name); + notify_counter_changed(counter_name, counters_.at(counter_name), + sc_time_stamp().to_default_time_units()); + return true; + } + } + catch(std::out_of_range &e) { + return false; + } +} + +bool PFPObject::decrement_counter(const std::string& counter_name, + const int decr_amount) { + try { + if (counters_.at(counter_name) == 0) { + return false; + } else { + counters_.at(counter_name) -= decr_amount; + notify_counter_changed(counter_name, counters_.at(counter_name), + sc_time_stamp().to_default_time_units()); + return true; + } + } + catch(std::out_of_range &e) { + return false; + } +} + +void PFPObject::attach_observer(std::shared_ptr observer) { + for (auto child : childModules_) { + child.second->attach_observer(observer); + } + observers_.push_back(observer); +} + +void PFPObject::notify_counter_changed(const std::string& counter_name, + std::size_t counter_value, double sim_time) { + for (auto& each_observer : observers_) { + auto func = std::bind(&PFPObserver::counter_updated, each_observer, + module_name(), counter_name, counter_value, sim_time); + events_.push(func); + } +} + +void PFPObject::notify_counter_added(const std::string& counter_name, + double sim_time) { + for (auto& each_observer : observers_) { + each_observer->counter_added(module_name(), counter_name, sim_time); + } +} + +void PFPObject::notify_counter_removed(const std::string& counter_name, + double sim_time) { + for (auto& each_observer : observers_) { + auto func = std::bind(&PFPObserver::counter_removed, each_observer, + module_name(), counter_name, sim_time); + events_.push(func); + } +} + +std::size_t PFPObject::num_counters() const { + return counters_.size(); +} + +void PFPObject::AddChildModule(std::string module_name, + PFPObject* module) { + childModules_[module_name] = module; +} + +}; // namespace core +}; // namespace pfp diff --git a/pfpsim/core/PFPObject.h b/pfpsim/core/PFPObject.h new file mode 100644 index 0000000..ca678c8 --- /dev/null +++ b/pfpsim/core/PFPObject.h @@ -0,0 +1,285 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef CORE_PFPOBJECT_H_ +#define CORE_PFPOBJECT_H_ +#include +#include +#include +#include "systemc.h" // NOLINT(build/include) +#include "tlm.h" // NOLINT(build/include) +#include "TrType.h" +#include "PFPConfig.h" +#include "LMTQueue.h" +#include "MTQueue.h" +#include "PFPObserver.h" +#include "./promptcolors.h" +#include "ConfigurationParameters.h" + +#define GetParam(key) get_param_value(configMap, key) +#define GetParamI(key) atoi(get_param_value(configMap, key).c_str()) + +/* + * Pre-Processor for NPU Log Levels. + */ + +// Format: CommonMacroName_No.ofargs + +// Concatenate macro name with number of inputs to base macro +#define CATMACRO(A, B) A ## B +// Select the name based on number of args. +#define SELECTMACRO(NAME, NUM) CATMACRO(NAME ## _, NUM) +// Get count for macros // Add here for additional number of arguments +#define GET_COUNT(_1, _2, COUNT, ...) COUNT +// Get args // Add here in reverse for additional number of args. +#define VA_SIZE(...) GET_COUNT(__VA_ARGS__, 2, 1) +#define VA_SELECT(NAME, ...) \ + SELECTMACRO(NAME, VA_SIZE(__VA_ARGS__))(__VA_ARGS__) // selector +#define npulog(...) VA_SELECT(LOG, __VA_ARGS__) // Base Macro + +#define npu_error(error_msg) { \ + cerr << "Error: " << (error_msg) << __FILE__ << ":" \ + << __LINE__ << endl; sc_stop(); } + +#define LOG_1(STAT)\ + if (pfp::core::PFPConfig::get().get_verbose_level() \ + != pfp::core::PFPConfig::get().verbosity::minimal \ + && pfp::core::PFPConfig::get().get_verbose_level() \ + == pfp::core::PFPConfig::get().verbosity::debug) { \ + sc_core::sc_time now = sc_time_stamp(); \ + cout << txtgrn << std::fixed << now.to_double()/1000 << " ns " \ + << txtrst << "@ " << txtrst << " "; \ + STAT \ + cout << txtrst; \ +} + +#define LOG_2(LEVEL, STAT) \ + if (pfp::core::PFPConfig::get().get_verbose_level() \ + == pfp::core::PFPConfig::get().verbosity::LEVEL) { \ + sc_core::sc_time now = sc_time_stamp(); \ + std::string modnametemp = module_name_; \ + cout << txtgrn << std::fixed << now.to_double()/1000 << " ns " \ + << txtrst << "@ " << On_Purple << modnametemp << txtrst << " "; \ + STAT \ + cout << txtrst; \ + } + +//------------------------------------------------------------------- + +namespace pfp { +namespace core { +inline std::string convert_to_string(sc_module_name nm) { + return static_cast(nm); +} + +class PFPObject { + public: + PFPObject(); + PFPObject(const std::string& module_name, PFPObject* parent = 0, + bool enable_dicp = false); + PFPObject(const std::string& module_name, std::string BaseConfigFile, + std::string InstanceConfigFile = "", PFPObject* parent = 0, + bool enable_dicp = false); + PFPObject(const PFPObject &other, bool enable_dicp = false); + virtual ~PFPObject() = default; + + /*--------Configuration of Modules -------------*/ + void LoadBaseConfiguration(std::string); + void LoadInstanceConfiguration(std::string); + + pfp::core::ConfigurationParameterNode GetParameter(std::string param); + pfp::core::ConfigurationParameterNode + GetParameterfromParent(std::string param, PFPObject* parent); + + std::vector ModuleHierarchy(); + /* ----- DICP ----- */ + const bool dicp_enabled; + + /* --- Module ---- */ + const std::string& module_name() const; + PFPObject* GetParent(); + + /* --- Observers --- */ + /** + * Add a counter to the PFPObject + * @param counter_name Name of the counter + * @param counter_value Value of the counter (default: 0) + * @return True if the counter was added; false if the counter already exists + */ + virtual bool add_counter(const std::string& counter_name, + std::size_t counter_value = 0); + /** + * Set the value of a counter + * @param counter_name Name of the counter + * @param counter_value Value of the counter + * @return True if the value was changed; false if the PFPObject does not contain the counter + */ + virtual bool set_counter(const std::string& counter_name, + std::size_t counter_value); + /** + * Remove a counter from the PFPObject + * @param counter_name Name of the counter + * @return True if the counter was removed; false if the PFPObject does not contain the counter + */ + virtual bool remove_counter(const std::string& counter_name); + /** + * Get the value of a counter + * Note: This will throw a std::out_of_range exception if the counter does not exist in the PFPObject + * @param counter_name Name of the counter + * @return Value of the counter + */ + virtual std::size_t counter_value(const std::string& counter_name) const; + /** + * Increment the specified counter + * @param counter_name Name of the counter + * @return True if the counter was incremented + */ + virtual bool increment_counter(const std::string& counter_name); + virtual bool increment_counter(const std::string& counter_name, + const int incr_amount); + /** + * Decrement the specified counter + * @param counter_name Name of the counter + * @return True if the counter was decremented + */ + virtual bool decrement_counter(const std::string& counter_name); + virtual bool decrement_counter(const std::string& counter_name, + const int decr_amount); + /** + * Attach an observer that will be notified when events occur + * @param observer Observer to attach + */ + virtual void attach_observer(std::shared_ptr observer); + /** + * Notify all attached observers when a counter is changed + * @param counter_name Name of the counter + * @param counter_value New value of the counter + * @param sim_time Simulation time at which the event occurred + */ + virtual void notify_counter_changed(const std::string& counter_name, + std::size_t counter_value, double sim_time); + /** + * Notify all attached observers when a counter is added to the PFPObject + * @param counter_name Name of the counter + * @param sim_time Simulation time at which the counter was added (this will usually be 0) + */ + virtual void notify_counter_added(const std::string& counter_name, + double sim_time); + /** + * Notify all attached observers when a counter is removed from the PFPObject + * @param counter_name Name of the counter + * @param sim_time Simulation time at which the counter was removed (this will usually be 0) + */ + virtual void notify_counter_removed(const std::string& counter_name, + double sim_time); + /** + * Get the number of counters in the PFPObject + * @return Number of counters + */ + virtual std::size_t num_counters() const; + /** + * Add a child module to the PFPObject + * @param module_name name of module + * @param module pointer to the module + */ + void AddChildModule(std::string module_name, PFPObject* module); + + // TODO(umair) why are all notify_data_* templated when PFPObserver only + // handles TrType anyway... + /** + * Notify all observers of data written + * @param data Data that was written + * @param sim_time Simulation time of the event + */ + template + void notify_data_written(const std::shared_ptr data, + double sim_time) { + for (auto& each_observer : observers_) { + auto func = std::bind(&PFPObserver::data_written, each_observer, + module_name(), data, sim_time); + events_.push(func); + } + } + /** + * Notify all observers of data read + * @param data Data that was read + * @param sim_time Simulation time of the event + */ + template + void notify_data_read(const std::shared_ptr data, + double sim_time) { + for (auto& each_observer : observers_) { + auto func = std::bind(&PFPObserver::data_read, each_observer, + module_name(), data, sim_time); + events_.push(func); + } + } + /** + * Notify all observers of data drop + * @param data Data that was dropped + * @param sim_time Simulation time of the event + */ + template + void notify_data_dropped(const std::shared_ptr data, + std::string& drop_reason, double sim_time) { + for (auto& each_observer : observers_) { + auto func = std::bind(&PFPObserver::data_dropped, each_observer, + module_name(), data, drop_reason, sim_time); + events_.push(func); + } + } + + template + void drop_data(const std::shared_ptr data, + std::string drop_reason) { + notify_data_dropped(data, + drop_reason, + sc_time_stamp().to_default_time_units()); + } + + pfp::core::ConfigurationParameterNode SimulationParameters; + static MTQueue> events_; /*!< Global event queue */ + + protected: + const std::string GlobalConfigPath; + const std::string module_name_; + PFPObject* parent_; /*!< Parent of this PFPObject */ + std::map configMap; /*!< Configuration Map used >*/ + //! Store counters and values + std::map counters_; + //! List of observers attached to this PFPObject + std::vector> observers_; + //! Internal list of submodules + std::map childModules_; +}; + +}; // namespace core +}; // namespace pfp +#endif // CORE_PFPOBJECT_H_ diff --git a/pfpsim/core/PFPObserver.h b/pfpsim/core/PFPObserver.h new file mode 100644 index 0000000..5efbbf6 --- /dev/null +++ b/pfpsim/core/PFPObserver.h @@ -0,0 +1,171 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * PFPObserver.h + * + * Created on: Jul 17, 2014 + * Author: kamil + */ +/** + * @file PFPObserver.h + * All observers of the PFP Simulations must inherit from interface PFPObserver. + * The observer must be registered with the NPU through the NPU's attach_observer(PFPObserver*) method. + */ +#ifndef CORE_PFPOBSERVER_H_ +#define CORE_PFPOBSERVER_H_ + +#include +#include + +namespace pfp { +namespace core { + +class PFPObserver { + public: + /** + * Function called by the NPU when a counter is added to a module + * @param module_name Module name to which counter was added + * @param counter_name Name of the counter + * @param simulation_time Simulation time at which counter was added (defaults to 0 since counters are typically added before simulation begins) + */ + virtual void counter_added(const std::string& module_name, + const std::string& counter_name, + double simulation_time = 0) = 0; + + /** + * Function called by the NPU when a counter is removed from a module + * @param module_name Module name from which counter was removed + * @param counter_name Name of the counter + * @param simulation_time Simulation time at which counter was removed (defaults to 0 since counters are typically removed before simulation begins) + */ + virtual void counter_removed(const std::string& module_name, + const std::string& counter_name, + double simulation_time = 0) = 0; + + /** + * Function called by the NPU when a counter is updated + * @param module_name Module containing updated counter + * @param counter_name Name of the counter + * @param new_value Current value of the counter + * @param simulation_time Simulation time at which counter value was updated + */ + virtual void counter_updated(const std::string& module_name, + const std::string& counter_name, + std::size_t new_value, + double simulation_time) = 0; + + /** + * Function called by the NPU when data is written by a module + * @param from_module Module name of the transmitting module + * @param data JSON representation of data + * @param simulation_time Simulation time at which event occurred + */ + virtual void data_written(const std::string& from_module, + const std::shared_ptr data, + double simulation_time) = 0; + + /** + * Function called by the NPU when data is read by a module + * @param to_module Module name of the receiving module + * @param data JSON representation of data + * @param simulation_time Simulation time at which event occurred + */ + virtual void data_read(const std::string& to_module, + const std::shared_ptr data, + double simulation_time) = 0; + + /** + * Function called by the NPU when data is dropped in a module + * @param in_module Module name in which data was dropped + * @param data JSON representation of data + * @param simulation_time Simulation time at which event occurred + */ + virtual void data_dropped(const std::string& in_module, + const std::shared_ptr data, + const std::string& drop_reason, + double simulation_time) = 0; + + /** + * Function called by the NPU when a TEU thread begins + * @param teu_mod TEU in which thread was started + * @param tec_mod TEC containing the TEU in which the thread was started + * @param thread_id ID number of the thread + * @param simulation_time Simulation time at which event occurred + */ + virtual void thread_begin(const std::string& teu_mod, + const std::string& tec_mod, + std::size_t thread_id, + std::size_t packet_id, + double simulation_time) = 0; + + /** + * Function called by the NPU when a TEU thread ends + * @param teu_mod TEU in which thread was ended + * @param tec_mod TEC containing the TEU in which the thread was ended + * @param thread_id ID number of the thread + * @param simulation_time Simulation time at which event occurred + */ + virtual void thread_end(const std::string& teu_mod, + const std::string& tec_mod, + std::size_t thread_id, + std::size_t packet_id, + double simulation_time) = 0; + + /** + * Function called by the NPU when a TEU thread starts idling + * @param teu_mod TEU in which thread is idling + * @param tec_mod TEC containing the TEU in which the thread is idling + * @param thread_id ID number of the thread + * @param simulation_time Simulation time at which event occurred + */ + virtual void thread_idle(const std::string& teu_mod, + const std::string& tec_mod, + std::size_t thread_id, + std::size_t packet_id, + double simulation_time) = 0; + + virtual void core_busy(const std::string& teu_mod, + const std::string& tec_mod, + double simulation_time) = 0; + + virtual void core_idle(const std::string& teu_mod, + const std::string& tec_mod, + double simulation_time) = 0; + + protected: + /** + * Default destructor + */ + virtual ~PFPObserver() = default; +}; +}; // namespace core +}; // namespace pfp +#endif // CORE_PFPOBSERVER_H_ diff --git a/pfpsim/core/PacketBase.h b/pfpsim/core/PacketBase.h new file mode 100644 index 0000000..b863439 --- /dev/null +++ b/pfpsim/core/PacketBase.h @@ -0,0 +1,68 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * PacketBase.h + */ + +#ifndef CORE_PACKETBASE_H_ +#define CORE_PACKETBASE_H_ +#include +#include "TrType.h" + +namespace pfp { +namespace core { + +// TODO(gordon) This class is only here for backwards compatibility. +// It could be removed. +class PacketBase: public TrType { + public: + PacketBase() = default; + virtual ~PacketBase() = default; + + explicit PacketBase(std::size_t id, std::string type = "PacketBase") + : TrType(id), type(type) {} + + std::string data_type() const override { + return type; + } + + bool debuggable() const override { + return true; + } + + private: + const std::string type; +}; // PacketBase + +}; // namespace core +}; // namespace pfp + +#endif // CORE_PACKETBASE_H_ diff --git a/pfpsim/core/StringUtils.cpp b/pfpsim/core/StringUtils.cpp new file mode 100644 index 0000000..ce2f0c5 --- /dev/null +++ b/pfpsim/core/StringUtils.cpp @@ -0,0 +1,71 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "StringUtils.h" +#include +#include +#include +#include +#include "PFPConfig.h" + +std::string to_hex_string(const void * key, size_t length) { + std::ostringstream ss; + ss << std::hex << std::uppercase << std::setfill('0'); + + for (size_t i = 0; i < length; ++i) { + ss << std::setw(2) + << (unsigned int)(reinterpret_cast(key))[i]; + } + if (pfp::core::PFPConfig::get().get_verbose_level() + == pfp::core::PFPConfig::get().verbosity::p4profile) { + std::cout << "Hex string is: " << ss.str() << std::endl; + } + return ss.str(); +} + +std::string to_binary_string(uint8_t * prefix, int width) { + std::string output(width, '0'); + int byte_index = 0; + int bit_index = 0; + uint8_t mask = 1 << 7; + for (; bit_index < width; ++bit_index, mask >>= 1) { + output[bit_index] = (prefix[byte_index] & mask) ? '1' : '0'; + if (!mask) { + mask = 1 << 7; + ++byte_index; + } + } + if (pfp::core::PFPConfig::get().get_verbose_level() + == pfp::core::PFPConfig::get().verbosity::p4profile) { + std::cout << " String uitls Binary prefix string is: " << output + << std::endl; + } + return output; +} diff --git a/pfpsim/core/StringUtils.h b/pfpsim/core/StringUtils.h new file mode 100644 index 0000000..82a81d4 --- /dev/null +++ b/pfpsim/core/StringUtils.h @@ -0,0 +1,41 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef CORE_STRINGUTILS_H_ +#define CORE_STRINGUTILS_H_ + +#include +#include + +std::string to_hex_string(const void * key, size_t length); + +std::string to_binary_string(uint8_t * prefix, int width); + +#endif // CORE_STRINGUTILS_H_ diff --git a/pfpsim/core/TrType.h b/pfpsim/core/TrType.h new file mode 100644 index 0000000..ed4b86c --- /dev/null +++ b/pfpsim/core/TrType.h @@ -0,0 +1,79 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * TrType.h + * + * Created on: Aug 13, 2015 + * Author: shafigh + */ + +#ifndef CORE_TRTYPE_H_ +#define CORE_TRTYPE_H_ + +#include +#include + +namespace pfp { +namespace core { + +class TrType { + public: + TrType() = default; + explicit TrType(std::size_t id):id_(id) { } + virtual ~TrType() = default; + virtual void id(std::size_t id) { + id_ = id; + } + virtual std::size_t id() const { + return id_; + } + virtual std::string data_type() const = 0; + + /** + * Check whether this packet should be watched by the debugger. + * By default return false, implementing classes that want to + * be debuggable should return true. Classes which want to be + * debugged should also be sure that distinct objects have + * distinct IDs + */ + virtual bool debuggable() const { + return false; + } + + private: + std::size_t id_; /* 2.3.0 +if("${SystemC_MAJOR}" MATCHES "") + EXEC_PROGRAM("cat ${_SYSTEMC_VERSION_FILE} | grep '#define SC_VERSION_MAJOR' | egrep -o '[0-9]' " + OUTPUT_VARIABLE SystemC_MAJOR) +endif() +if("${SystemC_MINOR}" MATCHES "") + EXEC_PROGRAM("cat ${_SYSTEMC_VERSION_FILE} | grep '#define SC_VERSION_MINOR' | egrep -o '[0-9]' " + OUTPUT_VARIABLE SystemC_MINOR) +endif() +if("${SystemC_REV}" MATCHES "") + EXEC_PROGRAM("cat ${_SYSTEMC_VERSION_FILE} | grep '#define SC_VERSION_PATCH' | egrep -o '[0-9]' " + OUTPUT_VARIABLE SystemC_REV) +endif() + +set(SystemC_VERSION ${SystemC_MAJOR}.${SystemC_MINOR}.${SystemC_REV}) + +# Kamil: The original code (commented out) is lazy +#if("${SystemC_MAJOR}" MATCHES "2") +# set(SystemC_FOUND TRUE) +#endif("${SystemC_MAJOR}" MATCHES "2") +if(NOT "${SystemC_MAJOR}" MATCHES "") + set(SystemC_FOUND TRUE) +endif() + +message(STATUS "SystemC version = ${SystemC_VERSION}") + +FIND_PATH(SystemC_INCLUDE_DIRS + NAMES systemc.h + HINTS ${_SYSTEMC_HINTS} + PATHS ${_SYSTEMC_PATHS} +) + +FIND_PATH(SystemC_LIBRARY_DIRS + NAMES libsystemc.a + HINTS ${_SYSTEMC_HINTS} + PATHS ${_SYSTEMC_PATHS} +) + +set(SystemC_LIBRARIES ${SystemC_LIBRARY_DIRS}/libsystemc.a) + +message(STATUS "SystemC library = ${SystemC_LIBRARIES}") diff --git a/pfpsim/core/cmake/modules/FindTLM.cmake b/pfpsim/core/cmake/modules/FindTLM.cmake new file mode 100644 index 0000000..48a9a48 --- /dev/null +++ b/pfpsim/core/cmake/modules/FindTLM.cmake @@ -0,0 +1,90 @@ +# - Find TLM +# This module finds if TLM is installed and determines where the +# include files and libraries are. This code sets the following +# variables: (from kernel/sc_ver.h) +# +# TLM_VERSION_STRING = Version of the package found, eg. "2.2.0" +# TLM_VERSION_MAJOR = The major version of the package found. +# TLM_VERSION_MINOR = The minor version of the package found. +# TLM_VERSION_PATCH = The patch version of the package found. +# TLM_VERSION_DATE = The date of release (from TLM_VERSION) +# TLM_VERSION = This is set to: $major.$minor.$patch +# +# The minimum required version of TLM can be specified using the +# standard CMake syntax, e.g. FIND_PACKAGE(TLM 2.2) +# +# For these components the following variables are set: +# +# TLM_FOUND - TRUE if all components are found. +# TLM_INCLUDE_DIRS - Full paths to all include dirs. +# TLM_LIBRARIES - Full paths to all libraries. +# TLM__FOUND - TRUE if is found. +# +# Example Usages: +# FIND_PACKAGE(TLM) +# FIND_PACKAGE(TLM 2.3) +# + +#============================================================================= +# Copyright 2012 GreenSocs +# Modified by Kamil Saigol for PFPSIM +#============================================================================= + +message(STATUS "Searching for TLM") + +# The HINTS option should only be used for values computed from the system. +SET(_TLM_HINTS + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\TLM\\2.2;TLMHome]/include" + $ENV{TLM_HOME} + ${SYSTEMC_PREFIX}/include + ${SYSTEMC_PREFIX}/lib + ${SYSTEMC_PREFIX}/lib-linux + ${SYSTEMC_PREFIX}/lib-linux64 + ${SYSTEMC_PREFIX}/lib-macos + $ENV{SYSTEMC_PREFIX}/include + $ENV{SYSTEMC_PREFIX}/lib + $ENV{SYSTEMC_PREFIX}/lib-linux + $ENV{SYSTEMC_PREFIX}/lib-linux64 + $ENV{SYSTEMC_PREFIX}/lib-macos + ${CMAKE_INSTALL_PREFIX}/include + ${CMAKE_INSTALL_PREFIX}/lib + ${CMAKE_INSTALL_PREFIX}/lib-linux + ${CMAKE_INSTALL_PREFIX}/lib-linux64 + ${CMAKE_INSTALL_PREFIX}/lib-macos + ) +# Hard-coded guesses should still go in PATHS. This ensures that the user +# environment can always override hard guesses. +# Kamil: I modified these paths from those provided by GreenSocs +SET(_TLM_PATHS + /usr/include/TLM + /usr/include/systemc + /usr/lib + /usr/lib-linux + /usr/lib-linux64 + /usr/lib-macos + /usr/local/lib + /usr/local/lib-linux + /usr/local/lib-linux64 + /usr/local/lib-macos + /usr/local/systemc + /usr/local/systemc/include + /usr/local/systemc/lib + /usr/local/systemc/lib-linux + /usr/local/systemc/lib-linux64 + ) + +FIND_PATH(TLM_INCLUDE_DIRS + NAMES tlm.h + HINTS ${_TLM_HINTS} + PATHS ${_TLM_PATHS} + PATH_SUFFIXES include/tlm +) + +EXEC_PROGRAM("ls ${TLM_INCLUDE_DIRS}/tlm.h" + RETURN_VALUE ret) + +if("${ret}" MATCHES "0") + set(TLM_FOUND TRUE) +endif("${ret}" MATCHES "0") + +message(STATUS "TLM library = ${TLM_INCLUDE_DIRS}/tlm.h") diff --git a/pfpsim/core/config.h.in b/pfpsim/core/config.h.in new file mode 100644 index 0000000..6611e89 --- /dev/null +++ b/pfpsim/core/config.h.in @@ -0,0 +1,9 @@ +#ifndef CORE_CONFIG_H_ +#define CORE_CONFIG_H_ + +#define PFPSIM_MAJOR_VERSION (@PFPSIM_MAJOR_VERSION@) +#define PFPSIM_MINOR_VERSION (@PFPSIM_MINOR_VERSION@) +#define PFPSIM_PATCH_VERSION (@PFPSIM_PATCH_VERSION@) +#define PFPSIM_VERSION_STRING "@PFPSIM_VERSION@" + +#endif // CORE_CONFIG_H_ diff --git a/pfpsim/core/cp/.gitignore b/pfpsim/core/cp/.gitignore new file mode 100644 index 0000000..ee041bc --- /dev/null +++ b/pfpsim/core/cp/.gitignore @@ -0,0 +1,9 @@ +*base.h +*.ipp +parser +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +Makefile +CommandParser.cpp +CommandScanner.cpp diff --git a/pfpsim/core/cp/CMakeLists.txt b/pfpsim/core/cp/CMakeLists.txt new file mode 100644 index 0000000..7641d2d --- /dev/null +++ b/pfpsim/core/cp/CMakeLists.txt @@ -0,0 +1,77 @@ +# +# PFPSim: Library for the Programmable Forwarding Plane Simulation Framework +# +# Copyright (C) 2016 Concordia Univ., Montreal +# Samar Abdi +# Umair Aftab +# Gordon Bailey +# Faras Dewal +# Shafigh Parsazad +# Eric Tremblay +# +# Copyright (C) 2016 Ericsson +# Bochra Boughzala +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + +cmake_minimum_required (VERSION 2.8.7) +set(${CMAKE_CXX_FLAGS} "${CMAKE_CXX_FLAGS} --std=c++11 -stdlib=libstdc++ -ggdb") +include(FindBISONCPP.cmake) +include(FindFLEXCPP.cmake) + +set(ParserClass "CommandParser" ) +set(ScannerClass "CommandScanner") + +BISONCPP_TARGET(${ParserClass} + ${CMAKE_CURRENT_SOURCE_DIR}/grammar.y + ${ParserClass}.ipp ${ParserClass} + COMPILE_FLAGS + "--scanner=${ScannerClass}.h --scanner-class-name=${ScannerClass}") + +FLEXCPP_TARGET (${ScannerClass} ${CMAKE_CURRENT_SOURCE_DIR}/tokens.l ${ScannerClass}.ipp ${ScannerClass}) + +ADD_FLEXCPP_BISONCPP_DEPENDENCY(${ScannerClass} ${ParserClass}) + +# For our NAMESPACE_HACK we need to wrap the generated c++ files +# This custom command lets cmake know about the dependenciy between +# The generated files and the wrapping for it. +add_custom_command(OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/${ParserClass}.cpp + COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/NAMESPACE_HACK_GEN_WRAPPER ${ParserClass} > ${CMAKE_CURRENT_SOURCE_DIR}/${ParserClass}.cpp + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${ParserClass}.ipp) +add_custom_command(OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/${ScannerClass}.cpp + COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/NAMESPACE_HACK_GEN_WRAPPER ${ScannerClass} > ${CMAKE_CURRENT_SOURCE_DIR}/${ScannerClass}.cpp + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${ScannerClass}.ipp) + +set(CP_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/Commands.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/${ParserClass}.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/${ScannerClass}.cpp + PARENT_SCOPE) + +set(CP_HEADERS + "${CMAKE_CURRENT_SOURCE_DIR}/${ParserClass}.h" + "${CMAKE_CURRENT_SOURCE_DIR}/${ScannerClass}.h" + "${CMAKE_CURRENT_SOURCE_DIR}/${ParserClass}base.h" + "${CMAKE_CURRENT_SOURCE_DIR}/${ScannerClass}base.h" + ${CMAKE_CURRENT_SOURCE_DIR}/Commands.h + ${CMAKE_CURRENT_SOURCE_DIR}/NAMESPACE_HACK_BEGIN + ${CMAKE_CURRENT_SOURCE_DIR}/NAMESPACE_HACK_END + PARENT_SCOPE) + +add_custom_target(cp +DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${ParserClass}.cpp +DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${ScannerClass}.cpp) diff --git a/pfpsim/core/cp/CommandParser.h b/pfpsim/core/cp/CommandParser.h new file mode 100644 index 0000000..2f645f4 --- /dev/null +++ b/pfpsim/core/cp/CommandParser.h @@ -0,0 +1,85 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Generated by Bisonc++ V4.09.02 on Sun, 03 Apr 2016 08:35:18 -0400 + +#ifndef CORE_CP_COMMANDPARSER_H_ +#define CORE_CP_COMMANDPARSER_H_ +#include +#include "Commands.h" + +#ifndef pfp_cpCommandParserBase_h_included +#include "NAMESPACE_HACK_BEGIN" +// $insert baseclass +#include "CommandParserbase.h" +#include "NAMESPACE_HACK_END" +#endif + +// $insert scanner.h +#include "CommandScanner.h" + +#include "NAMESPACE_HACK_BEGIN" // NOLINT(build/include) +// $insert namespace-open +namespace pfp_cp { + +#undef CommandParser +class CommandParser: public CommandParserBase { + // $insert scannerobject + CommandScanner d_scanner; + + public: + CommandParser(); + + std::shared_ptr parse_line(std::string & s); + + private: + void returnCommand(Command *cmd); + std::shared_ptr returned_command; + + int parse(); + void error(char const *msg); // called on (syntax) errors + int lex(); // returns the next token from the + // lexical scanner. + void print(); // use, e.g., d_token, d_loc + +// support functions for parse(): + void executeAction(int ruleNr); + void errorRecovery(); + int lookup(bool recovery); + void nextToken(); + void print__(); + void exceptionHandler__(std::exception const &exc); +}; + +// $insert namespace-close +}; // namespace pfp_cp +#include "NAMESPACE_HACK_END" // NOLINT(build/include) + +#endif // CORE_CP_COMMANDPARSER_H_ diff --git a/pfpsim/core/cp/CommandParser.ih b/pfpsim/core/cp/CommandParser.ih new file mode 100644 index 0000000..a8433d7 --- /dev/null +++ b/pfpsim/core/cp/CommandParser.ih @@ -0,0 +1,81 @@ +// Generated by Bisonc++ V4.09.02 on Sun, 03 Apr 2016 08:35:18 -0400 + + // Include this file in the sources of the class CommandParser. + +// $insert class.h +#include "CommandParser.h" + +#include "Commands.h" + +#include +#include +#include +#include +#include +#include + +#include "NAMESPACE_HACK_BEGIN" +// $insert namespace-open +namespace pfp_cp +{ + +CommandParser::CommandParser() +{ + // See https://fbb-git.github.io/bisoncpp/manual/bisonc++06.html#DVAL + d_scanner.setSval(&d_val__); +} + +inline void CommandParser::error(char const *msg) +{ + std::cerr << msg << '\n'; +} + +// $insert lex +inline int CommandParser::lex() +{ + return d_scanner.lex(); +} + +void CommandParser::returnCommand(Command * cmd){ + returned_command.reset(cmd); +} + +std::shared_ptr CommandParser::parse_line(std::string & s) { + std::stringstream is(s); + + d_scanner.switchStreams(is); + + returned_command.reset((Command*)nullptr); + this->parse(); + + return returned_command; +} + +inline void CommandParser::print() +{ + print__(); // displays tokens if --print was specified +} + +inline void CommandParser::exceptionHandler__(std::exception const &exc) +{ + throw; // re-implement to handle exceptions thrown by actions +} + +// $insert namespace-close +} +#include "NAMESPACE_HACK_END" + + // Add here includes that are only required for the compilation + // of CommandParser's sources. + + +// $insert namespace-use + // UN-comment the next using-declaration if you want to use + // symbols from the namespace pfp_cp without specifying pfp_cp:: +//using namespace pfp_cp; + + // UN-comment the next using-declaration if you want to use + // int CommandParser's sources symbols from the namespace std without + // specifying std:: + +//using namespace std; diff --git a/pfpsim/core/cp/CommandScanner.h b/pfpsim/core/cp/CommandScanner.h new file mode 100644 index 0000000..a13611d --- /dev/null +++ b/pfpsim/core/cp/CommandScanner.h @@ -0,0 +1,136 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Generated by Flexc++ V2.01.00 on Sun, 03 Apr 2016 08:35:18 -0400 + +#ifndef CORE_CP_COMMANDSCANNER_H_ +#define CORE_CP_COMMANDSCANNER_H_ +#include + +#ifndef pfp_cpCommandScannerBase_h_included +#include "NAMESPACE_HACK_BEGIN" +// $insert baseclass_h +#include "CommandScannerbase.h" +#include "NAMESPACE_HACK_END" +#endif + +#ifndef pfp_cpCommandParserBase_h_included +#include "NAMESPACE_HACK_BEGIN" // NOLINT(build/include) +#include "CommandParserbase.h" +#include "NAMESPACE_HACK_END" // NOLINT(build/include) +#endif + +// #define SCANNER_DEBUG 1 + +#include "NAMESPACE_HACK_BEGIN" // NOLINT(build/include) +// $insert namespace-open +namespace pfp_cp { + +// $insert classHead +class CommandScanner: public CommandScannerBase { + public: + explicit CommandScanner(std::istream &in = std::cin, + std::ostream &out = std::cout); + + CommandScanner(std::string const &infile, std::string const &outfile); + + // $insert lexFunctionDecl + int lex(); + // See https://fbb-git.github.io/bisoncpp/manual/bisonc++06.html#DVAL + void setSval(Meta__::SType *); + + private: + Meta__::SType * val; + + template + void returnValue(T); + + private: + int lex__(); + int executeAction__(size_t ruleNr); + + void print(); + void preCode(); // re-implement this function for code that must + // be exec'ed before the patternmatching starts + + void postCode(PostEnum__ type); + // re-implement this function for code that must + // be exec'ed after the rules's actions. +}; + +#ifdef SCANNER_DEBUG + #define returnToken(symbol) do { \ + std::cout << #symbol << std::endl; \ + return Parser::symbol;\ + } while (0) +#else + #define returnToken(symbol) return CommandParser::symbol +#endif + +template +void CommandScanner::returnValue(T t) { + val->get< Meta__::TagOf::tag >() = t; +} + +inline void CommandScanner::setSval(Meta__::SType * sval) { + val = sval; +} + +// $insert scannerConstructors +inline CommandScanner::CommandScanner(std::istream &in, std::ostream &out) +: + CommandScannerBase(in, out) +{} + +inline CommandScanner::CommandScanner(std::string const &infile, + std::string const &outfile): CommandScannerBase(infile, outfile) {} + +// $insert inlineLexFunction +inline int CommandScanner::lex() { + return lex__(); +} + +inline void CommandScanner::preCode() { + // optionally replace by your own code +} + +inline void CommandScanner::postCode(PostEnum__ type) { + // optionally replace by your own code +} + +inline void CommandScanner::print() { + print__(); +} + +// $insert namespace-close +}; // namespace pfp_cp +#include "NAMESPACE_HACK_END" // NOLINT(build/include) + +#endif // CORE_CP_COMMANDSCANNER_H_ diff --git a/pfpsim/core/cp/CommandScanner.ih b/pfpsim/core/cp/CommandScanner.ih new file mode 100644 index 0000000..a55bf94 --- /dev/null +++ b/pfpsim/core/cp/CommandScanner.ih @@ -0,0 +1,15 @@ +// Generated by Flexc++ V2.01.00 on Sun, 03 Apr 2016 08:35:18 -0400 + +// $insert class_h +#include "Commands.h" +#include "CommandScanner.h" + +#include +#include +#include +#include + +// $insert namespace-use + // UN-comment the next using-declaration if you want to use + // symbols from the namespace pfp_cp without prefixing pfp_cp:: +//using namespace pfp_cp; diff --git a/pfpsim/core/cp/Commands.cpp b/pfpsim/core/cp/Commands.cpp new file mode 100644 index 0000000..4a74d87 --- /dev/null +++ b/pfpsim/core/cp/Commands.cpp @@ -0,0 +1,289 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "Commands.h" + +#include +#include +#include +#include +#include +#include + +std::ostream & operator<< (std::ostream & os, const pfp::cp::Bytes & b) { + // Save state of stream + std::ios old_state(nullptr); + old_state.copyfmt(os); + + // Apply our iomanips and print the hex prefix + os << std::hex << std::setfill('0') << "0x"; + + // Print out the hex representation of our Bytes in MSB first order + for (auto it = b.rbegin(), end = b.rend(); it != end; ++it) { + os << std::setw(2) << static_cast(*it); + } + + // Restore state of stream + os.copyfmt(old_state); + + return os; +} + +std::ostream & operator<< (std::ostream & os, const pfp::cp::MatchKey & mk) { + mk.print(os); + return os; +} + +namespace pfp { +namespace cp { + +Action::Action(std::string name) : name(name) {} + +const std::string & Action::get_name() const { + return name; +} + +void Action::add_param(Bytes param) { + params.push_back(param); +} + +void Action::print() { + std::cout << " Action: " << name << "( " << std::endl; + for (auto & p : params) { + std::cout << " " << p << std::endl; + } + std::cout << " )" << std::endl; +} + +const std::vector & Action::get_params() const { + return params; +} + +MatchKey::MatchKey(Bytes v) + : data(v) {} + +const Bytes & MatchKey::get_data() const { + return data; +} + +size_t MatchKey::get_prefix_len() const { + assert(!"Not implemented"); + return 0; +} + +const Bytes & MatchKey::get_mask() const { + static Bytes b; + assert(!"Not implemented"); + return b; +} + +ExactKey::ExactKey(Bytes v) + : MatchKey(v) { + } + +MatchKey::Type ExactKey::get_type() const { + return MatchKey::Type::EXACT; +} + +void ExactKey::print(std::ostream & os) const { + os << data; +} + +LpmKey::LpmKey(Bytes data, size_t prefix_len) + :MatchKey(data), prefix_len(prefix_len) { + } + +MatchKey::Type LpmKey::get_type() const { + return MatchKey::Type::LPM; +} + +size_t LpmKey::get_prefix_len() const { + return prefix_len; +} + +void LpmKey::print(std::ostream & os) const { + os << data << " / " << prefix_len; +} + +TernaryKey::TernaryKey(Bytes data, Bytes mask) + :MatchKey(data), mask(mask) { } + +void TernaryKey::print(std::ostream & os) const { + os << data << " & " << mask; +} + +MatchKey::Type TernaryKey::get_type() const { + return MatchKey::Type::TERNARY; +} + +const Bytes & TernaryKey::get_mask() const { + return mask; +} + + +void Command::set_table_name(std::string s) { + table_name = s; +} + +const std::string & Command::get_table_name() const { + return table_name; +} + +std::shared_ptr Command::failure_result() { + return std::shared_ptr( + new FailedResult(this->shared_from_this())); +} + +void InsertCommand::print() { + std::cout << "Insert command!" << std::endl + << " table: " << table_name << std::endl; + for (auto & k : *keys) { + std::cout << " " << *k << std::endl; + } + action->print(); +} + +void InsertCommand::set_match_keys(MatchKeyContainer * v) { + keys.reset(v); +} + +void InsertCommand::set_action(Action * a) { + action.reset(a); +} + +const MatchKeyContainer & InsertCommand::get_keys() const { + return *keys; +} + +const Action & InsertCommand::get_action() const { + return * action; +} + +std::shared_ptr InsertCommand::success_result(size_t handle) { + return std::shared_ptr( + new InsertResult(this->shared_from_this(), handle)); +} + +void ModifyCommand::print() { + std::cout << "Modify command!" << std::endl + << " table : " << table_name << std::endl + << " handle: " << handle << std::endl; + action->print(); +} + +void ModifyCommand::set_handle(size_t h) { + handle = h; +} + +size_t ModifyCommand::get_handle() const { + return handle; +} + +void ModifyCommand::set_action(Action * a) { + action.reset(a); +} + +const Action & ModifyCommand::get_action() const { + return *action; +} + +std::shared_ptr ModifyCommand::success_result() { + return std::shared_ptr( + new ModifyResult(this->shared_from_this())); +} + +void DeleteCommand::print() { + std::cout << "Delete command!" << std::endl + << " table : " << table_name << std::endl + << " handle: " << handle << std::endl; +} +void DeleteCommand::set_handle(size_t h) { + handle = h; +} + +size_t DeleteCommand::get_handle() const { + return handle; +} + +std::shared_ptr DeleteCommand::success_result() { + return std::shared_ptr( + new DeleteResult(this->shared_from_this())); +} + +void BootCompleteCommand::print() { + std::cout << "Boot Complete Command!" << std::endl; +} + +// Control plane agent passes itself to the message ... +std::shared_ptr +CommandProcessor::accept_command(const std::shared_ptr & cmd) { + return cmd->process(this); +} + +// The message then passes itself back to the Control Plane Agent with the +// correct type +#define PROCESS(TYPE) \ + std::shared_ptr TYPE::process(CommandProcessor * cp) { \ + return cp->process(this); \ + } + +PROCESS(InsertCommand) +PROCESS(ModifyCommand) +PROCESS(DeleteCommand) +PROCESS(BootCompleteCommand) + +#undef PROCESS + + +void ResultProcessor::accept_result( + const std::shared_ptr & res) { + return res->process(this); +} + +CommandResult::CommandResult(std::shared_ptr cmd) + : command(cmd) {} + +InsertResult::InsertResult(std::shared_ptr cmd, size_t handle) + : CommandResult(cmd), handle(handle) {} + +#define PROCESS(TYPE) \ + void TYPE::process(ResultProcessor * rp) { \ + return rp->process(this); \ + } + +PROCESS(InsertResult) +PROCESS(ModifyResult) +PROCESS(DeleteResult) +PROCESS(FailedResult) + +#undef PROCESS + +}; // namespace cp +}; // namespace pfp diff --git a/pfpsim/core/cp/Commands.h b/pfpsim/core/cp/Commands.h new file mode 100644 index 0000000..8e19fa6 --- /dev/null +++ b/pfpsim/core/cp/Commands.h @@ -0,0 +1,297 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#ifndef CORE_CP_COMMANDS_H_ +#define CORE_CP_COMMANDS_H_ + +#include +#include +#include +#include +#include + +namespace pfp { +namespace cp { + +typedef std::vector Bytes; + +// Command Parameter types + + +class Action { + public: + explicit Action(std::string name); + void add_param(Bytes param); + void print(); + const std::vector & get_params() const; + const std::string & get_name() const; + private: + std::string name; + std::vector params; +}; + +class MatchKey { + public: + enum Type { + EXACT, LPM, TERNARY + }; + + explicit MatchKey(Bytes v); + + virtual void print(std::ostream & os) const = 0; + virtual ~MatchKey() = default; + + const Bytes & get_data() const; + virtual size_t get_prefix_len() const; + virtual const Bytes & get_mask() const; + virtual Type get_type() const = 0; + protected: + const Bytes data; +}; + + +class ExactKey : public MatchKey { + public: + explicit ExactKey(Bytes v); + void print(std::ostream & os) const override; + virtual ~ExactKey() = default; + Type get_type() const override; +}; + +class LpmKey : public MatchKey { + public: + LpmKey(Bytes data, size_t prefix_len); + void print(std::ostream & os) const override; + virtual ~LpmKey() = default; + size_t get_prefix_len() const override; + Type get_type() const override; + + private: + const size_t prefix_len; +}; + +class TernaryKey : public MatchKey { + public: + TernaryKey(Bytes data, Bytes mask); + void print(std::ostream & os) const override; + virtual ~TernaryKey() = default; + const Bytes & get_mask() const override; + Type get_type() const override; + + private: + const Bytes mask; +}; + +typedef std::unique_ptr MatchKeyUPtr; +typedef std::vector MatchKeyContainer; + +// Commands + +class CommandProcessor; +class CommandResult; +class ResultProcessor; + +// enable_shared_from_this allows the commands to pass a smart pointer to +// themselves to the result that they generate, so that the processor of the +// result has information about the command it came from +class Command : public std::enable_shared_from_this { + public: + virtual void print() = 0; + virtual std::shared_ptr process(CommandProcessor *p) = 0; + virtual void set_table_name(std::string s); + const std::string & get_table_name() const; + virtual ~Command() = default; + + // Generate a failure response + std::shared_ptr failure_result(); + + protected: + std::string table_name; +}; + +#define OVERRIDE_PROCESS() \ + std::shared_ptr process(CommandProcessor *p) override + +class InsertCommand : public Command { + public: + void print() override; + OVERRIDE_PROCESS(); + void set_match_keys(MatchKeyContainer * v); + void set_action(Action * a); + virtual ~InsertCommand() = default; + const MatchKeyContainer & get_keys() const; + const Action & get_action() const; + + // Build a response for a successful insert command. The handle should + // be some type of unique identifier for the inserted entry. + std::shared_ptr success_result(size_t handle); + + private: + std::unique_ptr keys; + std::unique_ptr action; +}; + +class ModifyCommand : public Command { + public: + void print() override; + OVERRIDE_PROCESS(); + void set_handle(size_t h); + size_t get_handle() const; + void set_action(Action * a); + const Action & get_action() const; + virtual ~ModifyCommand() = default; + + std::shared_ptr success_result(); + + private: + size_t handle; + std::unique_ptr action; +}; + +class DeleteCommand : public Command { + public: + void print() override; + OVERRIDE_PROCESS(); + void set_handle(size_t h); + size_t get_handle() const; + virtual ~DeleteCommand() = default; + + std::shared_ptr success_result(); + + private: + size_t handle; +}; + +class BootCompleteCommand : public Command { + public: + void print() override; + OVERRIDE_PROCESS(); + virtual ~BootCompleteCommand() = default; +}; + +#undef OVERRIDE_PROCESS + +// CommandProcessor + +#define DECLARE_PROCESS(TYPE) \ + friend class TYPE; \ + virtual std::shared_ptr process(TYPE* t) = 0 + +class CommandProcessor { + public: + std::shared_ptr + accept_command(const std::shared_ptr & cmd); + + protected: + DECLARE_PROCESS(InsertCommand); + DECLARE_PROCESS(ModifyCommand); + DECLARE_PROCESS(DeleteCommand); + DECLARE_PROCESS(BootCompleteCommand); +}; +#undef DECLARE_PROCESS + + +// Results + +#define OVERRIDE_PROCESS() \ + void process(ResultProcessor *p) override + +class CommandResult { + public: + virtual ~CommandResult() = default; + virtual void process(ResultProcessor *p) = 0; + + explicit CommandResult(std::shared_ptr cmd); + + public: + const std::shared_ptr command; +}; + +class InsertResult : public CommandResult { + public: + virtual ~InsertResult() = default; + OVERRIDE_PROCESS(); + + InsertResult(std::shared_ptr cmd, size_t handle); + + const size_t handle; +}; + +class ModifyResult : public CommandResult { + public: + virtual ~ModifyResult() = default; + OVERRIDE_PROCESS(); + + using CommandResult::CommandResult; +}; + +class DeleteResult : public CommandResult { + public: + virtual ~DeleteResult() = default; + OVERRIDE_PROCESS(); + + using CommandResult::CommandResult; +}; + +class FailedResult : public CommandResult { + public: + virtual ~FailedResult() = default; + OVERRIDE_PROCESS(); + + using CommandResult::CommandResult; + + const std::string message; +}; +#undef OVERRIDE_PROCESS + +#define DECLARE_PROCESS(TYPE) \ + friend class TYPE; \ + virtual void process(TYPE* t) = 0 + +class ResultProcessor { + public: + void accept_result(const std::shared_ptr & res); + + protected: + DECLARE_PROCESS(ModifyResult); + DECLARE_PROCESS(InsertResult); + DECLARE_PROCESS(DeleteResult); + DECLARE_PROCESS(FailedResult); +}; +#undef DECLARE_PROCESS + + +}; // namespace cp +}; // namespace pfp + +std::ostream & operator<< (std::ostream & os, const pfp::cp::Bytes & b); +std::ostream & operator<< (std::ostream & os, const pfp::cp::MatchKey & k); + +#endif // CORE_CP_COMMANDS_H_ diff --git a/pfpsim/core/cp/FindBISONCPP.cmake b/pfpsim/core/cp/FindBISONCPP.cmake new file mode 100644 index 0000000..e561bf0 --- /dev/null +++ b/pfpsim/core/cp/FindBISONCPP.cmake @@ -0,0 +1,202 @@ +#.rst: +# FindBISON +# --------- +# +# Find ``bison`` executable and provide a macro to generate custom build rules. +# +# The module defines the following variables: +# +# ``BISONCPP_EXECUTABLE`` +# path to the ``bison`` program +# +# ``BISONCPP_VERSION`` +# version of ``bison`` +# +# ``BISONCPP_FOUND`` +# true if the program was found +# +# The minimum required version of ``bison`` can be specified using the +# standard CMake syntax, e.g. ``find_package(BISON 2.1.3)``. +# +# If ``bison`` is found, the module defines the macro:: +# +# BISONCPP_TARGET( +# [COMPILE_FLAGS ] +# [VERBOSE ] +# ) +# +# which will create a custom rule to generate a parser. ```` is +# the path to a yacc file. ```` is the name of the source file +# generated by bison. A header file is also be generated, and contains +# the token list. +# +# The options are: +# +# ``COMPILE_FLAGS `` +# Specify flags to be added to the ``bison`` command line. +# +# ``VERBOSE `` +# Tell ``bison`` to write verbose descriptions of the grammar and +# parser to the given ````. +# +# The macro defines the following variables: +# +# ``BISONCPP__DEFINED`` +# true is the macro ran successfully +# +# ``BISONCPP__INPUT`` +# The input source file, an alias for +# +# ``BISONCPP__OUTPUT_SOURCE`` +# The source file generated by bison +# +# ``BISONCPP__OUTPUT_HEADER`` +# The header file generated by bison +# +# ``BISONCPP__OUTPUTS`` +# The sources files generated by bison +# +# ``BISONCPP__COMPILE_FLAGS`` +# Options used in the ``bison`` command line +# +# Example usage: +# +# .. code-block:: cmake +# +# find_package(BISON) +# BISONCPP_TARGET(MyParser parser.y ${CMAKE_CURRENT_BINARY_DIR}/parser.cpp +# DEFINES_FILE ${CMAKE_CURRENT_BINARY_DIR}/parser.h) +# add_executable(Foo main.cpp ${BISONCPP_MyParser_OUTPUTS}) + +#============================================================================= +# Copyright 2009 Kitware, Inc. +# Copyright 2006 Tristan Carel +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +find_program(BISONCPP_EXECUTABLE NAMES bisonc++ DOC "path to the bison executable") +mark_as_advanced(BISONCPP_EXECUTABLE) + +include(CMakeParseArguments) + +if(BISONCPP_EXECUTABLE) + # the bison commands should be executed with the C locale, otherwise + # the message (which are parsed) may be translated + set(_Bison_SAVED_LC_ALL "$ENV{LC_ALL}") + set(ENV{LC_ALL} C) + + execute_process(COMMAND ${BISONCPP_EXECUTABLE} --version + OUTPUT_VARIABLE BISONCPP_version_output + ERROR_VARIABLE BISONCPP_version_error + RESULT_VARIABLE BISONCPP_version_result + OUTPUT_STRIP_TRAILING_WHITESPACE) + + set(ENV{LC_ALL} ${_Bison_SAVED_LC_ALL}) + + if(NOT ${BISONCPP_version_result} EQUAL 0) + message(SEND_ERROR "Command \"${BISONCPP_EXECUTABLE} --version\" failed with output:\n${BISONCPP_version_error}") + else() + if("${BISONCPP_version_output}" MATCHES "^bisonc\\+\\+ V([^,]+)") + set(BISONCPP_VERSION "${CMAKE_MATCH_1}") + endif() + endif() + + # internal macro + macro(BISONCPP_TARGET_option_verbose Name BisonOutput filename) + list(APPEND BISONCPP_TARGET_cmdopt "--verbose") + get_filename_component(BISONCPP_TARGET_output_path "${BisonOutput}" PATH) + get_filename_component(BISONCPP_TARGET_output_name "${BisonOutput}" NAME_WE) + add_custom_command(OUTPUT ${filename} + COMMAND ${CMAKE_COMMAND} + ARGS -E copy + "${BISONCPP_TARGET_output_path}/${BISONCPP_TARGET_output_name}.output" + "${filename}" + DEPENDS + "${BISONCPP_TARGET_output_path}/${BISONCPP_TARGET_output_name}.output" + COMMENT "[BISONC++][${Name}] Copying bison verbose table to ${filename}" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + set(BISONCPP_${Name}_VERBOSE_FILE ${filename}) + list(APPEND BISONCPP_TARGET_extraoutputs + "${BISONCPP_TARGET_output_path}/${BISONCPP_TARGET_output_name}.output") + endmacro() + + # internal macro + macro(BISONCPP_TARGET_option_extraopts Options) + set(BISONCPP_TARGET_extraopts "${Options}") + separate_arguments(BISONCPP_TARGET_extraopts) + list(APPEND BISONCPP_TARGET_cmdopt ${BISONCPP_TARGET_extraopts}) + endmacro() + + #============================================================ + # BISONCPP_TARGET (public macro) + #============================================================ + # + macro(BISONCPP_TARGET Name BisonInput BisonOutput BisonClass) + set(BISONCPP_TARGET_output_header "${CMAKE_CURRENT_SOURCE_DIR}/${BisonClass}base.h") + set(BISONCPP_TARGET_cmdopt "") + set(BISONCPP_TARGET_outputs "${CMAKE_CURRENT_SOURCE_DIR}/${BisonOutput}") + + # Parsing parameters + set(BISONCPP_TARGET_PARAM_OPTIONS) + set(BISONCPP_TARGET_PARAM_ONE_VALUE_KEYWORDS + VERBOSE + COMPILE_FLAGS + ) + set(BISONCPP_TARGET_PARAM_MULTI_VALUE_KEYWORDS) + cmake_parse_arguments( + BISONCPP_TARGET_ARG + "${BISONCPP_TARGET_PARAM_OPTIONS}" + "${BISONCPP_TARGET_PARAM_ONE_VALUE_KEYWORDS}" + "${BISONCPP_TARGET_PARAM_MULTI_VALUE_KEYWORDS}" + ${ARGN} + ) + + if(NOT "${BISONCPP_TARGET_ARG_UNPARSED_ARGUMENTS}" STREQUAL "") + message(SEND_ERROR "Usage") + else() + if(NOT "${BISONCPP_TARGET_ARG_VERBOSE}" STREQUAL "") + BISONCPP_TARGET_option_verbose(${Name} ${BisonOutput} "${BISONCPP_TARGET_ARG_VERBOSE}") + endif() + if(NOT "${BISONCPP_TARGET_ARG_COMPILE_FLAGS}" STREQUAL "") + BISONCPP_TARGET_option_extraopts("${BISONCPP_TARGET_ARG_COMPILE_FLAGS}") + endif() + + list(APPEND BISONCPP_TARGET_cmdopt "--class-name=${BisonClass}") + list(APPEND BISONCPP_TARGET_outputs "${BISONCPP_TARGET_output_header}") + + add_custom_command(OUTPUT ${BISONCPP_TARGET_outputs} + ${BISONCPP_TARGET_extraoutputs} + COMMAND ${BISONCPP_EXECUTABLE} + ARGS ${BISONCPP_TARGET_cmdopt} --no-lines --parsefun-source=${BisonOutput} ${BisonInput} + DEPENDS ${BisonInput} + COMMENT "[BISONC++][${Name}] Building parser with bisonc++ ${BISONCPP_VERSION}" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + + # define target variables + set(BISONCPP_${Name}_DEFINED TRUE) + set(BISONCPP_${Name}_INPUT ${BisonInput}) + set(BISONCPP_${Name}_OUTPUTS ${BISONCPP_TARGET_outputs}) + set(BISONCPP_${Name}_COMPILE_FLAGS ${BISONCPP_TARGET_cmdopt}) + set(BISONCPP_${Name}_OUTPUT_SOURCE "${BisonOutput}") + set(BISONCPP_${Name}_OUTPUT_HEADER "${BISONCPP_TARGET_output_header}") + + endif() + endmacro() + # + #============================================================ + +endif() + +find_package(PackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(BISON REQUIRED_VARS BISONCPP_EXECUTABLE + VERSION_VAR BISONCPP_VERSION) + +# FindBISON.cmake ends here diff --git a/pfpsim/core/cp/FindFLEXCPP.cmake b/pfpsim/core/cp/FindFLEXCPP.cmake new file mode 100644 index 0000000..a527594 --- /dev/null +++ b/pfpsim/core/cp/FindFLEXCPP.cmake @@ -0,0 +1,218 @@ +#.rst: +# FindFLEX +# -------- +# +# Find flex executable and provides a macro to generate custom build rules +# +# +# +# The module defines the following variables: +# +# :: +# +# FLEXCPP_FOUND - true is flex executable is found +# FLEXCPP_EXECUTABLE - the path to the flex executable +# FLEXCPP_VERSION - the version of flex +# FLEXCPP_LIBRARIES - The flex libraries +# FLEXCPP_INCLUDE_DIRS - The path to the flex headers +# +# +# +# The minimum required version of flex can be specified using the +# standard syntax, e.g. find_package(FLEXCPP 2.5.13) +# +# +# +# If flex is found on the system, the module provides the macro: +# +# :: +# +# FLEXCPP_TARGET(Name FlexInput FlexOutput +# [COMPILE_FLAGS ] +# ) +# +# which creates a custom command to generate the file from +# the file. If COMPILE_FLAGS option is specified, the next +# parameter is added to the flex command line. If flex is configured to +# output a header file, the DEFINES_FILE option may be used to specify its +# name. Name is an alias used to get details of this custom command. +# Indeed the macro defines the following variables: +# +# :: +# +# FLEXCPP_${Name}_DEFINED - true is the macro ran successfully +# FLEXCPP_${Name}_OUTPUTS - the source file generated by the custom rule, an +# alias for FlexOutput +# FLEXCPP_${Name}_INPUT - the flex source file, an alias for ${FlexInput} +# FLEXCPP_${Name}_OUTPUT_HEADER - the header flex output, if any. +# +# +# +# Flex scanners oftenly use tokens defined by Bison: the code generated +# by Flex depends of the header generated by Bison. This module also +# defines a macro: +# +# :: +# +# ADD_FLEXCPP_BISONCPP_DEPENDENCY(FlexTarget BisonTarget) +# +# which adds the required dependency between a scanner and a parser +# where and are the first parameters of +# respectively FLEXCPP_TARGET and BISONCPP_TARGET macros. +# +# :: +# +# ==================================================================== +# Example: +# +# +# +# :: +# +# find_package(BISON) +# find_package(FLEX) +# +# +# +# :: +# +# BISONCPP_TARGET(MyParser parser.y ${CMAKE_CURRENT_BINARY_DIR}/parser.cpp) +# FLEXCPP_TARGET(MyScanner lexer.l ${CMAKE_CURRENT_BINARY_DIR}/lexer.cpp) +# ADD_FLEXCPP_BISONCPP_DEPENDENCY(MyScanner MyParser) +# +# +# +# :: +# +# include_directories(${CMAKE_CURRENT_BINARY_DIR}) +# add_executable(Foo +# Foo.cc +# ${BISONCPP_MyParser_OUTPUTS} +# ${FLEXCPP_MyScanner_OUTPUTS} +# ) +# ==================================================================== + +#============================================================================= +# Copyright 2009 Kitware, Inc. +# Copyright 2006 Tristan Carel +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +find_program(FLEXCPP_EXECUTABLE NAMES flexc++ DOC "path to the flex executable") +mark_as_advanced(FLEXCPP_EXECUTABLE) + +find_library(FL_LIBRARY NAMES fl + DOC "Path to the fl library") + +find_path(FLEXCPP_INCLUDE_DIR FlexLexer.h + DOC "Path to the flex headers") + +mark_as_advanced(FL_LIBRARY FLEXCPP_INCLUDE_DIR) + +include(CMakeParseArguments) + +set(FLEXCPP_INCLUDE_DIRS ${FLEXCPP_INCLUDE_DIR}) +set(FLEXCPP_LIBRARIES ${FL_LIBRARY}) + +if(FLEXCPP_EXECUTABLE) + + execute_process(COMMAND ${FLEXCPP_EXECUTABLE} --version + OUTPUT_VARIABLE FLEXCPP_version_output + ERROR_VARIABLE FLEXCPP_version_error + RESULT_VARIABLE FLEXCPP_version_result + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT ${FLEXCPP_version_result} EQUAL 0) + message(SEND_ERROR "Command \"${FLEXCPP_EXECUTABLE} --version\" failed with output:\n${FLEXCPP_version_error}") + else() + if("${FLEXCPP_version_output}" MATCHES "^flexc\\+\\+ V([^,]+)") + set(FLEXCPP_VERSION "${CMAKE_MATCH_1}") + endif() + endif() + + #============================================================ + # FLEXCPP_TARGET (public macro) + #============================================================ + # + macro(FLEXCPP_TARGET Name Input Output Classname) + set(FLEXCPP_TARGET_outputs "${CMAKE_CURRENT_SOURCE_DIR}/${Output}") + set(FLEXCPP_EXECUTABLE_opts "") + + set(FLEXCPP_TARGET_PARAM_OPTIONS) + set(FLEXCPP_TARGET_PARAM_ONE_VALUE_KEYWORDS + COMPILE_FLAGS + ) + set(FLEXCPP_TARGET_PARAM_MULTI_VALUE_KEYWORDS) + + cmake_parse_arguments( + FLEXCPP_TARGET_ARG + "${FLEXCPP_TARGET_PARAM_OPTIONS}" + "${FLEXCPP_TARGET_PARAM_ONE_VALUE_KEYWORDS}" + "${FLEXCPP_TARGET_MULTI_VALUE_KEYWORDS}" + ${ARGN} + ) + + set(FLEXCPP_TARGET_usage "FLEXCPP_TARGET( [COMPILE_FLAGS ]") + + if(NOT "${FLEXCPP_TARGET_ARG_UNPARSED_ARGUMENTS}" STREQUAL "") + message(SEND_ERROR ${FLEXCPP_TARGET_usage}) + else() + if(NOT "${FLEXCPP_TARGET_ARG_COMPILE_FLAGS}" STREQUAL "") + set(FLEXCPP_EXECUTABLE_opts "${FLEXCPP_TARGET_ARG_COMPILE_FLAGS}") + separate_arguments(FLEXCPP_EXECUTABLE_opts) + endif() + + set(FLEXCPP_OUTPUT_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/${Classname}base.h") + list(APPEND FLEXCPP_TARGET_outputs "${FLEXCPP_OUTPUT_HEADER}") + + add_custom_command(OUTPUT ${FLEXCPP_TARGET_outputs} + COMMAND ${FLEXCPP_EXECUTABLE} + ARGS ${FLEXCPP_EXECUTABLE_opts} --no-lines --lex-source=${Output} --class-name=${Classname} ${Input} + DEPENDS ${Input} + COMMENT "[FLEXC++][${Name}] Building scanner with flexc++ ${FLEXCPP_VERSION}" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + + + set(FLEXCPP_${Name}_DEFINED TRUE) + set(FLEXCPP_${Name}_OUTPUTS ${FLEXCPP_TARGET_outputs}) + set(FLEXCPP_${Name}_INPUT ${Input}) + set(FLEXCPP_${Name}_COMPILE_FLAGS ${FLEXCPP_EXECUTABLE_opts}) + set(FLEXCPP_${Name}_OUTPUT_HEADER "${FLEXCPP_OUTPUT_HEADER}") + endif() + endmacro() + #============================================================ + + + #============================================================ + # ADD_FLEXCPP_BISONCPP_DEPENDENCY (public macro) + #============================================================ + # + macro(ADD_FLEXCPP_BISONCPP_DEPENDENCY FlexTarget BisonTarget) + + if(NOT FLEXCPP_${FlexTarget}_OUTPUTS) + message(SEND_ERROR "Flex target `${FlexTarget}' does not exist.") + endif() + + if(NOT BISONCPP_${BisonTarget}_OUTPUT_HEADER) + message(SEND_ERROR "Bison target `${BisonTarget}' does not exist.") + endif() + + set_source_files_properties(${FLEXCPP_${FlexTarget}_OUTPUTS} + PROPERTIES OBJECT_DEPENDS ${BISONCPP_${BisonTarget}_OUTPUT_HEADER}) + endmacro() + #============================================================ + +endif() + +find_package(PackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(FLEXCPP REQUIRED_VARS FLEXCPP_EXECUTABLE + VERSION_VAR FLEXCPP_VERSION) + +# FindFLEX.cmake ends here diff --git a/pfpsim/core/cp/NAMESPACE_HACK_BEGIN b/pfpsim/core/cp/NAMESPACE_HACK_BEGIN new file mode 100644 index 0000000..cfaf9e1 --- /dev/null +++ b/pfpsim/core/cp/NAMESPACE_HACK_BEGIN @@ -0,0 +1,29 @@ +// The purpose of this file is to work around the limitation that bisonc++ and +// flexc++ will only generate a single namespace, but we want our parser to +// live in a nested namespace. To get around this, we are using the following: + +#define pfp_cp pfp { namespace cp + +// This changes the single namespace declaration into a declaration of two +// nested namespaces. Of course this comes with many problems, not least of +// which is that it makes the code using this hack a little confusing. +// +// Because this generates an additional opening-brace, we have a sister file +// called NAMESPACE_HACK_END. This file includes a single closing-brace, as +// well as a macro which un-defines the name pfp_cp to avoid polluting the +// macro namespace with this bizarre define. +// +// Because headers files may be included multiple times with include-guards, +// we sometimes have to wrap our entire usage of this hack in the include- +// guard of the hacked file. You can see this in CommandParser.h and in +// CommandScanner.h +// +// Finally in order to wrap the actual cpp file, we've changed its extension +// to .ipp, and then created a cpp file which simply includes that .ipp file +// surrounded by this hack. In that special case, we've added a macro called +// NAMESPACE_HACK_DONTCLEAN which disables the #undef previously described +// in NAMESPACE_HACK_END. This is because otherwise #includes within the +// .ipp file would clean the #define when it is still needed, and we don't +// care about polluting the macro namespace inside that particular cpp file. +// +// author: Gordon Bailey | gb at gordonbailey dot net diff --git a/pfpsim/core/cp/NAMESPACE_HACK_END b/pfpsim/core/cp/NAMESPACE_HACK_END new file mode 100644 index 0000000..b28d052 --- /dev/null +++ b/pfpsim/core/cp/NAMESPACE_HACK_END @@ -0,0 +1,6 @@ +} +#ifdef pfp_cp +#ifndef NAMESPACE_HACK_DONTCLEAN +#undef pfp_cp +#endif +#endif diff --git a/pfpsim/core/cp/NAMESPACE_HACK_GEN_WRAPPER b/pfpsim/core/cp/NAMESPACE_HACK_GEN_WRAPPER new file mode 100755 index 0000000..5d8d967 --- /dev/null +++ b/pfpsim/core/cp/NAMESPACE_HACK_GEN_WRAPPER @@ -0,0 +1,7 @@ +#!/bin/sh + +echo "#define NAMESPACE_HACK_DONTCLEAN" +echo "#include \"NAMESPACE_HACK_BEGIN\"" +echo "#include \"$1.ipp\"" +echo "#include \"NAMESPACE_HACK_END\"" + diff --git a/pfpsim/core/cp/README.md b/pfpsim/core/cp/README.md new file mode 100644 index 0000000..ef5401d --- /dev/null +++ b/pfpsim/core/cp/README.md @@ -0,0 +1,31 @@ +Control Plane Command Parser +------------------- + +This is a parser which accepts control-plane commands, for example insert_entry +which should be executed by the switch. It's a complete rewrite of the old +`MatchActionConfig.cpp` hand-written parser to be easier to understand and +to extend, and less brittle. + + +Adding new commands +---------------------- +Adding new commands should be relatively straightforward. + + - Create a new subclass of Command to represent your command + - Don't forget to override the `process` function. For convenience this can + be done with the `OVERRIDE_PROCESS` macro in the class declaration, and by + the `PROCESS` macro in the `.cpp` file + - Register your command with the ControlPlaneAgent interface by adding a + `DECLARE_PROCESS` line. + - In `grammar.y`: + - Create a polymorphic type for your command in the main `%polymorphic` + section. + - Create a token for your command and associate it to your type in the + `Terminal types` section. + - Create a production for your command. Follow the examples of `insert_entry_command` + and the other pre-existing commands. + - Add your command grammar rule to the overall `command` production. + - In `tokens.l` create a new reserved word for the command. Look at the + insert_entry for an example. + - That's it. + diff --git a/pfpsim/core/cp/grammar.y b/pfpsim/core/cp/grammar.y new file mode 100644 index 0000000..eafbdd7 --- /dev/null +++ b/pfpsim/core/cp/grammar.y @@ -0,0 +1,313 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +%namespace pfp_cp + +%scanner Scanner.h +%scanner-token-function d_scanner.lex() + +/* There are 2 shift-reduce conflict created by allowing trailing whitespace */ +/* these are for the productions ending in an action spec, because they end */ +/* with a whitespace-seperated list of bytes, so it's not immediately clear */ +/* if there will be another argument following whitespace, or if it is just */ +/* extra. */ +/* */ +/* There's a final shift-reduce conflict in the rule for comments and blank */ +/* lines, since actually every symbol is is nullable (I think that's why) */ +%expect 3 + /* Basic data types*/ +%polymorphic INTEGER : unsigned long long; + BYTES : Bytes; + STRING : std::string; + + /* Action type, defined in Commands.h */ + ACTION_SPEC : Action *; + + /* Match Key Types, defined in Commands.h */ + MATCH_KEY : MatchKey *; + EXACT_KEY_PARAM : ExactKey *; + LPM_KEY_PARAM : LpmKey *; + TERNARY_KEY_PARAM : TernaryKey *; + + /* Group of Match Keys */ + MATCH_KEYS : MatchKeyContainer *; + + /* Command Types, defined in Commands.h */ + COMMAND : Command *; + INSERT_COMMAND : InsertCommand *; + MODIFY_COMMAND : ModifyCommand *; + DELETE_COMMAND : DeleteCommand *; + + +/***** Nonterminal types *****/ +/* Keys */ +%type exact_match_key +%type lpm_match_key +%type ternary_match_key +%type match_key +%type match_keys + +/* Actions */ +%type action_spec +%type action_param + +/* Commands */ +%type insert_entry_command +%type modify_entry_command +%type delete_entry_command +%type command + +/* Data */ +%type bytes +%type raw_bytes +%type ip_addr + + +/***** Terminal types *****/ +/* Table or action names */ +%token IDENTIFIER +%type IDENTIFIER + +/* Reserved words */ +%token INSERT_ENTRY +%type INSERT_ENTRY + +%token MODIFY_ENTRY +%type MODIFY_ENTRY + +%token DELETE_ENTRY +%type DELETE_ENTRY + +/* Literal values */ +%token DECIMAL +%type DECIMAL + +%token HEXADECIMAL +%type HEXADECIMAL + +/* Whitespace tokens */ +%token __ EOL +%token COMMENT + + +%% + +/* A sentence in our language is just a single command. + * We will also allow leading and trailing spaces and + * a trailing newline, to avoid being overly sensitive. + */ +startrule: + optional_space command optional_EOL { + returnCommand($2); + }| + optional_space optional_comment optional_EOL { + returnCommand(nullptr); + }; + +/* The list of the commands we recognize */ +/* TODO: there must be a more elegant way of dealing with this casting */ +/* in bisonc++ 4.09 this is unnecassary but in 4.05 (ubuntu 14.04's version)*/ +/* we have to do this... :( */ +command: + insert_entry_command{ + $$ = static_cast($1); + }| + modify_entry_command{ + $$ = static_cast($1); + }| + delete_entry_command{ + $$ = static_cast($1); + }; + +/* Command Formats */ +insert_entry_command: + INSERT_ENTRY __ IDENTIFIER __ match_keys __ action_spec { + $1 ->set_table_name($3); + $1 ->set_match_keys($5); + $1 ->set_action($7); + $$ = $1; + }; + +modify_entry_command: + MODIFY_ENTRY __ IDENTIFIER __ DECIMAL __ action_spec { + $1 -> set_table_name($3); + $1 -> set_handle($5); + $1 -> set_action($7); + $$ = $1; + }; + +delete_entry_command: + DELETE_ENTRY __ IDENTIFIER __ DECIMAL { + $1 -> set_table_name($3); + $1 -> set_handle($5); + $$ = $1; + }; + +/* match_keys is a collection of zero or more match keys */ +match_keys: + /* Create an empty list */ + empty { + $$ = new MatchKeyContainer(); + }| + /* Create a list with one key in it */ + match_key { + $$ = new MatchKeyContainer(); + $$ -> push_back(MatchKeyUPtr($1)); + }| + /* Add a key to the existing list */ + match_keys __ match_key { + $$ = $1; + $$ -> push_back(MatchKeyUPtr($3)); + }; + +/* The types of match keys we can represent */ +/* TODO same problem as Commands above, there must be a better way */ +match_key: + exact_match_key{ + $$ = static_cast($1); + }| + lpm_match_key{ + $$ = static_cast($1); + }| + ternary_match_key{ + $$ = static_cast($1); + }; + +/* An exact match key is just any chunk of bytes */ +exact_match_key: + bytes { + $$ = new ExactKey($1); + }; + +/* An LPM key is a chunk of bytes a slash and then a decimal prefix length */ +/* Note that no spaces are permitted */ +lpm_match_key: + bytes '/' DECIMAL { + $$ = new LpmKey($1, $3); + }; + +/* A ternary key is a chunk of bytes an '&' and another chunk of bytes */ +/* Note that no spaces are permitted */ +ternary_match_key: + bytes '&' bytes { + $$ = new TernaryKey($1, $3); + }; + +/* An action spec is an action name, followed by a list of action parameters */ +action_spec: + /* An identifier of the action name creates a new Action */ + IDENTIFIER { + $$ = new Action($1); + }| + /* Add a new parameter to the existing action spec */ + action_spec __ action_param { + $$ = $1; + $$ ->add_param($3); + }; + +/* for now an action param is just any sequence of bytes */ +action_param: bytes; + +/* bytes is any literal, with an optional resizing */ +bytes: + /* bytes without resizing */ + raw_bytes| + /* bytes, resized to a specified length */ + raw_bytes '\'' DECIMAL { + auto newsize = $3; + auto bytes = $1; + + // Resize used to work because the important part of the data was + // at the beginning, now it's at the end so we need to copy it forward + + auto delta = std::abs((int)bytes.size() - (int)newsize); + + if (newsize < bytes.size()) { + // we're shrinking the data so we need to copy the range + // [delta, max_index] to [0, max_index-delta] + + // copy data forward + std::copy(bytes.begin() + delta, bytes.end(), bytes.begin()); + // shrink to the new size + bytes.resize(newsize); + } else if(newsize > bytes.size()) { + // Here we're growing the data so we need to copy the range + // [0, max_index] to [delta, max_index+delta] + + // TODO - there's some problem with this that comes up under valgrind + // shows up only for size >= 16 ... :S + + // expand to the new size + bytes.resize(newsize); + // copy data to the end + std::copy(bytes.begin(), bytes.end()+1-delta, bytes.begin()+delta); + // Zero out the beginning (where the data was before + std::fill(bytes.begin(), bytes.end()+1-delta, 0x00); + } + + //$1 .resize($3, 0x00); + $$ = bytes; + }; + +/* Any literal representation which can be converted to a sequence of bytes */ +raw_bytes: + DECIMAL { + Bytes ret_val; + // We cast the unsigned long long to a buffer of bytes + uint8_t * buf = (uint8_t*) & $1; + size_t len = sizeof $1; + + // We then just copy that buffer into our vector + std::reverse_copy(buf, buf+len, back_inserter(ret_val)); + + $$ = ret_val; + }| + HEXADECIMAL| + ip_addr; + +/* An ipv4 address in the usual format */ +/* We parse it in the grammar instead of as a terminal token for the sake + of simplicity */ +ip_addr: + DECIMAL '.' DECIMAL '.' DECIMAL '.' DECIMAL + { + // Simply create a vector of the four decimal values + $$ = Bytes{ + (uint8_t)$1, + (uint8_t)$3, + (uint8_t)$5, + (uint8_t)$7}; + }; + +/* Utility productions */ +optional_space: __ | empty; +optional_EOL: optional_space EOL | optional_space; +optional_comment: COMMENT | empty; +empty: ; diff --git a/pfpsim/core/cp/main.cc b/pfpsim/core/cp/main.cc new file mode 100644 index 0000000..9e58882 --- /dev/null +++ b/pfpsim/core/cp/main.cc @@ -0,0 +1,60 @@ +#include +#include +#include +#include "CommandParser.h" +#include "Commands.h" + +using namespace std; +using namespace pfp::cp; + +class MiniCPA : public CommandProcessor { + protected: + std::shared_ptr process(InsertCommand * cmd) override { + cout << "CPA Received an insert command:" << endl; + cmd->debug_print(); + return std::shared_ptr(new InsertResult(cmd->shared_from_this(), 1)); + } + + std::shared_ptr process(ModifyCommand * cmd) override { + cout << "CPA Received a modify command:" << endl; + cmd->debug_print(); + return std::shared_ptr(new ModifyResult(cmd->shared_from_this()));; + } + + std::shared_ptr process(DeleteCommand * cmd) override { + cout << "CPA Received a delete command:" << endl; + cmd->debug_print(); + return std::shared_ptr(new DeleteResult(cmd->shared_from_this())); + } + + std::shared_ptr process(BootCompleteCommand * cmd) override { + cout << "CPA Received a boot-complete command:" << endl; + cmd->debug_print(); + return nullptr; + } + +}; + + +int main(int argc, const char *argv[]) +{ + pfp::cp::CommandParser parser; + MiniCPA cpa; + + std::string line; + do { + std::getline(std::cin, line); + const auto cmd = parser.parse_line(line); + + if (cmd.get()) { + cpa.accept_command(cmd); + } else { + cout << "Skipping comment line" << endl; + } + + } while( ! std::cin.eof() ); + + cpa.accept_command(std::make_shared()); + + return 0; +} diff --git a/pfpsim/core/cp/test b/pfpsim/core/cp/test new file mode 100755 index 0000000..0799306 --- /dev/null +++ b/pfpsim/core/cp/test @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +extra="" + +if [ "$1" == "vg" ] +then + extra="valgrind --leak-check=full --track-origins=yes" +fi + + +( + ### All of the entries from table.txt which we had been previously using ### + echo "insert_entry ipv4_lpm 10.0.0.0/8 set_nhop 10.0.1.1 1" + echo "insert_entry ipv4_lpm 10.0.1.2/32 set_nhop 10.0.1.2 2" + + echo "insert_entry forward 10.0.1.1 set_dmac FF:FF:FF:FF:FF:01" + echo "insert_entry forward 10.0.1.2 set_dmac BB:BB:BB:BB:BB:01" + + echo "insert_entry send_frame 1'2 rewrite_mac AA:AA:AA:AA:AA:01" + echo "insert_entry send_frame 2'2 rewrite_mac AA:AA:AA:AA:AA:02" + + echo "insert_entry tcp_rewrite_dst 10000'2 rewrite_tcp_dst 12358" + + echo "insert_entry tcp_rewrite_src 10000'2 rewrite_tcp_dst 12358" + + ### Modify entries ### + echo "modify_entry ipv4_lpm 2 set_nhop 34.56.78.90 100" + + ### Delete Entries ### + echo "delete_entry huge_table 18446744073709551615" # probably to make room for more :P + + # Blank line + echo + + # Comment lines + echo "# no leading space" + echo " # leading space" + + ### Now featuring ternary! ### + echo "insert_entry ternary_table 192.168.0.1&ff:ff:0f:00 some_action 456" + + # Resizing numbers + echo "insert_entry a 1'1 foo" + echo "insert_entry a 1'2 foo" + echo "insert_entry a 1'3 foo" + echo "insert_entry a 1'4 foo" + echo "insert_entry a 1'5 foo" + echo "insert_entry a 1'6 foo" + echo "insert_entry a 1'7 foo" + echo "insert_entry a 1'8 foo" + echo "insert_entry a 1'9 foo" + echo "insert_entry a 1'10 foo" + echo "insert_entry a 1'12 foo" + echo "insert_entry a 1'13 foo" + echo "insert_entry a 1'14 foo" + echo "insert_entry a 1'15 foo" + echo "insert_entry a 1'16 foo" + echo "insert_entry a 1'17 foo" + +) | $extra ./parser diff --git a/pfpsim/core/cp/tokens.l b/pfpsim/core/cp/tokens.l new file mode 100644 index 0000000..7c8f219 --- /dev/null +++ b/pfpsim/core/cp/tokens.l @@ -0,0 +1,142 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +%namespace pfp_cp + +%% + +insert_entry { + // Reserved word, creates an empty command object + returnValue(new InsertCommand()); + returnToken(INSERT_ENTRY); +} + +modify_entry { + // Reserved word, creates an empty command object + returnValue(new ModifyCommand()); + returnToken(MODIFY_ENTRY); +} + +delete_entry { + // Reserved word, creates an empty command object + returnValue(new DeleteCommand()); + returnToken(DELETE_ENTRY); +} + +#.* { + returnToken(COMMENT); +} + +[[:digit:]]+ { + // Matches a positive decimal literal + + // We attempt to convert the literal to an unsigned long long + // since its the biggest type we can easily use without having + // to mess around with bignum libraries, and probably no one + // will need to specify a number that big anyways. + unsigned long long int_val; + try { + int_val = std::stoull(matched()); + }catch(std::out_of_range){ + // If the value is too big, print an error (TODO npulog?) and + // return a NULL to cause a syntax error + std::cerr << "Decimal literal " << matched() << " is too big!" << std::endl; + return '\0'; + } + + // Return the value as a DECIMAL token + returnValue( int_val ); + returnToken(DECIMAL); +} + +[[:xdigit:]]{2}(:[[:xdigit:]]{2})+ { + // Matches a string of bytes specified as hexadecimal with bytes + // seperated from eachother with colons. This is to match the typical + // format for MAC addresses, but is more general, allowing longer + // and shorter bytes strings to be specified as well. + + // We parse the matched string into a vector of uint8_t (Bytes) + std::string s = matched(); + Bytes ret_val; + + // We will parse bytes starting from the first one, so that we + // end up with an array in MSB first order. + + // This is the position of the first byte in the string + size_t pos = 0; + + // Now we loop over, incrementing by three characters (two hex digits + // and a colon), converting each pair of hex digits to an integer and + // pushing it back into our Bytes vector. + while (pos < s.size()) { + ret_val.push_back( (uint8_t) std::stoi( s.substr(pos, 2), nullptr, 16 )); + pos += 3; + } + + // Return the Bytes as a HEXADECIMAL token + returnValue(ret_val); + returnToken(HEXADECIMAL); +} + +[[:alpha:]_][[:alpha:][:digit:]_]* { + // Matches an identifier, which could be the name of a table + // or an action. Standard regex for a c-style variable name + returnValue(matched()); + returnToken(IDENTIFIER); +} + +[ \t]+ { + // Spaces are returned as tokens so that we can have constructs + // in the grammar which are built out of multiple tokens, but in + // which we don't want to allow spaces. One example is LPM keys + // such as 192.168.0.1/23 where we do not allow spaces between + // data, '/' character, and prefix. Another example is any data + // where we explicitly specify a length, for example 123'4, where + // again we don't allow spaces. + returnToken(__); +} + +\n { + // Explicitly handle newlines, because we don't want to allow them. + returnToken(EOL); +} + +. { + // This token is just here for debugging purposes, it simply does + // what would already be done automatically, that is literally + // returning any character not otherwise matched. The reason it's + // here is so that we can optionally print out the returned character + // for debugging. Unfortunately we can't use our usual returnToken + // macro, since it expects the name of a token. + #ifdef SCANNER_DEBUG + std::cout << '\'' << matched()[0] << '\'' << std::endl; + #endif + return matched()[0]; +} diff --git a/pfpsim/core/debugger/Breakpoint.cpp b/pfpsim/core/debugger/Breakpoint.cpp new file mode 100644 index 0000000..47cbe6b --- /dev/null +++ b/pfpsim/core/debugger/Breakpoint.cpp @@ -0,0 +1,74 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "Breakpoint.h" +#include +#include + +namespace pfp { +namespace core { +namespace db { + +int Breakpoint::next_id = 0; + +Breakpoint::Breakpoint(bool stealth): temp(false), disabled(false) { + if (!stealth) { + id = next_id++; + } else { + id = -1; + } +} + +bool Breakpoint::isEqual(Breakpoint br) { + for (auto it = conditions.begin(); it != conditions.end(); it++) { + try { + if ((br.conditions.at(it->first)) != it->second) { + return false; + } + } catch (std::out_of_range e) { + return false; + } + } + return true; +} + +void Breakpoint::addCondition(BreakpointCondition condition, + std::string value) { + conditions.insert(std::pair(condition, value)); +} + +int Breakpoint::getID() const { + return id; +} + +}; // namespace db +}; // namespace core +}; // namespace pfp diff --git a/pfpsim/core/debugger/Breakpoint.h b/pfpsim/core/debugger/Breakpoint.h new file mode 100644 index 0000000..1592ee5 --- /dev/null +++ b/pfpsim/core/debugger/Breakpoint.h @@ -0,0 +1,114 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** +* @file Breakpoint.h +* Defines a class representation of a breakpoint. +* +* Created on: February 3, 2016 +* Author: Eric Tremblay +*/ + +#ifndef CORE_DEBUGGER_BREAKPOINT_H_ +#define CORE_DEBUGGER_BREAKPOINT_H_ + +#include +#include +#include + +namespace pfp { +namespace core { +namespace db { + +/** + * @brief Class representation of a pfpdb breakpoint. + */ +class Breakpoint { + public: + /** + * Enumeration representing the various conditions on which the user can break. + * The user can break when a module reads a packet, a module writes a packet, a specific simulation time is reached or when a packet hits any module. + */ + enum BreakpointCondition { + BREAK_ON_MODULE_READ, + BREAK_ON_MODULE_WRITE, + BREAK_AT_TIME, + BREAK_ON_PACKET_ID + }; + + /** + * Default Constructor + * @param stealth Indicates whether the breakpoint is internal or set by the user. + */ + explicit Breakpoint(bool stealth = false); + + /** + * Function to compare two Breakpoint objects. + * @param br object to compare to. + * @return true if they are equal, otherwise false. + */ + bool isEqual(Breakpoint br); + + /** + * Add a condition to the Breakpoint. + * @param condition condition to be added. + * @param value value to break on. + */ + void addCondition(BreakpointCondition condition, std::string value); + + /** + * Get the unique ID of the Breakpoint + * @return the unique ID. + */ + int getID() const; + + //! Map containing all the conditions of the Breakpoint + //! and the associated value. + std::map conditions; + //! Indicates whether the Breakpoint is temporary. + //! If temporary, it will be destroyed when hit. + bool temp; + //! Indicates whether the Breakpoint is disabled. + //! If disabled, it will not be hit but will still exist so that it + //! can be enabled later. + bool disabled; + + private: + //! Unique identifier + int id; + //! Maintains the ID of the next Breakpoint that will be created. + static int next_id; +}; + +}; // namespace db +}; // namespace core +}; // namespace pfp + +#endif // CORE_DEBUGGER_BREAKPOINT_H_ diff --git a/pfpsim/core/debugger/CMakeLists.txt b/pfpsim/core/debugger/CMakeLists.txt new file mode 100644 index 0000000..5fabb2d --- /dev/null +++ b/pfpsim/core/debugger/CMakeLists.txt @@ -0,0 +1,55 @@ +# +# PFPSim: Library for the Programmable Forwarding Plane Simulation Framework +# +# Copyright (C) 2016 Concordia Univ., Montreal +# Samar Abdi +# Umair Aftab +# Gordon Bailey +# Faras Dewal +# Shafigh Parsazad +# Eric Tremblay +# +# Copyright (C) 2016 Ericsson +# Bochra Boughzala +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# + +set(DEBUGGER_SRC +${DEBUGGER_SRC} +${CMAKE_CURRENT_SOURCE_DIR}/DebugObserver.cpp +${CMAKE_CURRENT_SOURCE_DIR}/DebuggerMessages.cpp +${CMAKE_CURRENT_SOURCE_DIR}/DebuggerIPCServer.cpp +${CMAKE_CURRENT_SOURCE_DIR}/DebugDataManager.cpp +${CMAKE_CURRENT_SOURCE_DIR}/Breakpoint.cpp +${CMAKE_CURRENT_SOURCE_DIR}/DebuggerPacket.cpp +${CMAKE_CURRENT_SOURCE_DIR}/Watchpoint.cpp +${CMAKE_CURRENT_SOURCE_DIR}/CPDebuggerInterface.cpp +PARENT_SCOPE +) + +set(DEBUGGER_HEADERS +${DEBUGGER_HEADERS} +${CMAKE_CURRENT_SOURCE_DIR}/DebugObserver.h +${CMAKE_CURRENT_SOURCE_DIR}/DebuggerMessages.h +${CMAKE_CURRENT_SOURCE_DIR}/DebuggerIPCServer.h +${CMAKE_CURRENT_SOURCE_DIR}/DebugDataManager.h +${CMAKE_CURRENT_SOURCE_DIR}/Breakpoint.h +${CMAKE_CURRENT_SOURCE_DIR}/DebuggerPacket.h +${CMAKE_CURRENT_SOURCE_DIR}/Watchpoint.h +${CMAKE_CURRENT_SOURCE_DIR}/CPDebuggerInterface.h +PARENT_SCOPE +) diff --git a/pfpsim/core/debugger/CPDebuggerInterface.cpp b/pfpsim/core/debugger/CPDebuggerInterface.cpp new file mode 100644 index 0000000..9a6e752 --- /dev/null +++ b/pfpsim/core/debugger/CPDebuggerInterface.cpp @@ -0,0 +1,178 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "CPDebuggerInterface.h" +#include +#include +#include + +namespace pfp { +namespace core { +namespace db { + +void CPDebuggerInterface::updateHandle(std::string table_name, + std::string match_key, std::string action_name, uint64_t handle) { + if (table_entries.find(table_name) != table_entries.end()) { + if (table_entries[table_name].find(action_name) + != table_entries[table_name].end()) { + std::vector& entries = table_entries[table_name][action_name]; + for (auto it = entries.begin(); it != entries.end(); it++) { + if (it->match_key == match_key) { + it->handle = handle; + it->status = CPDebuggerInterface::TableEntryStatus::OK; + break; + } + } + } + } +} + +void CPDebuggerInterface::addTableEntry(std::string table_name, + std::string match_key, std::string action_name, + std::vector action_data, uint64_t handle) { + CPDebuggerInterface::TableEntry entry(table_name, match_key, action_name, + action_data, handle, CPDebuggerInterface::TableEntryStatus::INSERTING); + if (table_entries.find(table_name) == table_entries.end()) { + std::vector entry_vector; + entry_vector.push_back(entry); + std::map> action_map; + action_map[action_name] = entry_vector; + table_entries[table_name] = action_map; + } else { + std::map>& action_map_it + = table_entries[table_name]; + if (action_map_it.find(action_name) == action_map_it.end()) { + std::vector entry_vector; + entry_vector.push_back(entry); + action_map_it[action_name] = entry_vector; + } else { + std::vector& entries_vector_it = action_map_it[action_name]; + entries_vector_it.push_back(entry); + } + } +} + +void CPDebuggerInterface::printTableEntries() { + std::cout << "Table Entries:" << std::endl; + for (auto table = table_entries.begin(); + table != table_entries.end(); table++) { + std::cout << table->first << ":" << std::endl; + for (auto action = table->second.begin(); + action != table->second.end(); action++) { + std::cout << "\t" << action->first << ":" << std::endl; + for (auto entry = action->second.begin(); + entry != action->second.end(); entry++) { + std::cout << "\t\t" << entry->match_key << " - " + << entry->handle << std::endl; + for (auto data = entry->action_data.begin(); + data != entry->action_data.end(); data++) { + std::cout << "\t\t\t" << *data << std::endl; + } + } + } + } +} + +void CPDebuggerInterface::updateTableEntry(std::string table_name, + uint64_t handle, std::string action_name, + std::vector action_data) { + std::vector& entries = table_entries[table_name][action_name]; + for (auto it = entries.begin(); it != entries.end(); it++) { + if (it->handle == handle) { + it->action_data = action_data; + it->status = CPDebuggerInterface::TableEntryStatus::MODIFYING; + break; + } + } +} + +void CPDebuggerInterface::deleteTableEntry(std::string table_name, + uint64_t handle) { + std::map>& action_map + = table_entries[table_name]; + for (auto action = action_map.begin(); action != action_map.end(); action++) { + for (auto entry = action->second.begin(); + entry != action->second.end(); entry++) { + if (entry->handle == handle) { + entry->status = CPDebuggerInterface::TableEntryStatus::DELETING; + break; + } + } + } +} + +std::vector +CPDebuggerInterface::getAllTableEntries() { + std::vector entries; + for (auto i = table_entries.begin(); i != table_entries.end(); i++) { + for (auto j = i->second.begin(); j != i->second.end(); j++) { + entries.insert(entries.end(), j->second.begin(), j->second.end()); + } + } + return entries; +} + +void CPDebuggerInterface::confirmUpdateEntry(std::string table_name, + uint64_t handle) { + std::map>& action_map + = table_entries[table_name]; + for (auto action = action_map.begin(); action != action_map.end(); action++) { + for (auto entry = action->second.begin(); + entry != action->second.end(); entry++) { + if (entry->handle == handle) { + entry->status = CPDebuggerInterface::TableEntryStatus::OK; + break; + } + } + } +} + +void CPDebuggerInterface::confirmDeleteEntry(std::string table_name, + uint64_t handle) { + std::map>& action_map + = table_entries[table_name]; + for (auto action = action_map.begin(); action != action_map.end(); action++) { + for (auto entry = action->second.begin(); + entry != action->second.end(); entry++) { + if (entry->handle == handle) { + action->second.erase(entry); + if (action->second.size() == 0) { + action_map.erase(action); + } + break; + } + } + } +} + +}; // namespace db +}; // namespace core +}; // namespace pfp diff --git a/pfpsim/core/debugger/CPDebuggerInterface.h b/pfpsim/core/debugger/CPDebuggerInterface.h new file mode 100644 index 0000000..3006328 --- /dev/null +++ b/pfpsim/core/debugger/CPDebuggerInterface.h @@ -0,0 +1,160 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** +* @file CPDebuggerInterface.h +* Defines an interface to be implemented by Control Plane modules for compatibility with the debugger. +* +* Created on: March 2, 2016 +* Author: Eric Tremblay +*/ + +#ifndef CORE_DEBUGGER_CPDEBUGGERINTERFACE_H_ +#define CORE_DEBUGGER_CPDEBUGGERINTERFACE_H_ + +#include +#include +#include +#include + +namespace pfp { +namespace core { +namespace db { + +/** + * Interface for Control Plane module that is necessary for it to work with pfpdb. + * It defines what operations must be supported for the debugger to work. + * Provides functions to maintain a list of entries so that it may be accessed by the debugger. + * It is the user's responsibility to call these functions at the appropriate time so that the debugger remains up-to-date. + */ +class CPDebuggerInterface { + public: + enum TableEntryStatus { + OK, + INSERTING, + DELETING, + MODIFYING, + NONE + }; + + /** + * Data Structure used to represent table entries so that they can be stored. + */ + class TableEntry { + public: + TableEntry() {} + TableEntry(std::string table_name, std::string match_key, + std::string action_name, std::vector action_data, + uint64_t handle, CPDebuggerInterface::TableEntryStatus status = NONE) + : table_name(table_name), match_key(match_key), + action_name(action_name), action_data(action_data), + handle(handle), status(status) {} + + //! Name of table in which the entry is found. + std::string table_name; + //! Key used to perform the match. + std::string match_key; + //! Name of the action that is used if this entry is hit. + std::string action_name; + //! Data inserted into the action if this entry is hit. + std::vector action_data; + //! Handle (or unique identifier) corresponding to this entry. + uint64_t handle; + CPDebuggerInterface::TableEntryStatus status; + }; + + virtual void do_command(std::string command) = 0; + + /** + * Update the handle of an entry in internal list of entries. Useful if the handle is not known when addTableEntry is called + * This handle is necessary for modifying or deleting an entry. + * @param table_name Name of table in which the entry is found. + * @param match_key Key of the entry. + * @param action_name Name of the action of the entry. + * @param handle New unique identifier for entry. + */ + void updateHandle(std::string table_name, std::string match_key, + std::string action_name, uint64_t handle); + + /** + * Add a table entry to internal list of entries + * @param table_name Name of table to which the entry belongs. + * @param match_key Match key for the entry. + * @param action_name Name of action associated with entry. + * @param action_data Data used with the action for this entry. + */ + void addTableEntry(std::string table_name, std::string match_key, + std::string action_name, std::vector action_data, + uint64_t handle = -1); + + /** + * Print the internal list of entries to stdout. Useful for debugging. + */ + void printTableEntries(); + + /** + * Update or modify an entry that has already been added to the internal list of entries. + * @param table_name Name of table to which the entry belongs. + * @param handle Unique identifier of the entry. + * @param action_name Name of the new action for the entry. + * @param action_data The new data to be used with the action. + */ + void updateTableEntry(std::string table_name, uint64_t handle, + std::string action_name, std::vector action_data); + + /** + * Delete an entry from the internal list of entries. + * @param table_name [description] + * @param handle [description] + */ + void deleteTableEntry(std::string table_name, uint64_t handle); + + // confirm an update/modify + void confirmUpdateEntry(std::string table_name, uint64_t handle); + + // confirm a update / delete + void confirmDeleteEntry(std::string table_name, uint64_t handle); + + /** + * Get internal list of table entries. + */ + std::vector getAllTableEntries(); + + protected: + //! List of table entries (in a more convenient data structure) + std::map>> table_entries; +}; + +}; // namespace db +}; // namespace core +}; // namespace pfp + +#endif // CORE_DEBUGGER_CPDEBUGGERINTERFACE_H_ diff --git a/pfpsim/core/debugger/DebugDataManager.cpp b/pfpsim/core/debugger/DebugDataManager.cpp new file mode 100644 index 0000000..fa2370c --- /dev/null +++ b/pfpsim/core/debugger/DebugDataManager.cpp @@ -0,0 +1,307 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "DebugDataManager.h" +#include +#include +#include +#include +#include + +namespace pfp { +namespace core { +namespace db { + +DebugDataManager::DebugDataManager() + : simulation_time(0.0), + current_packet_id(-1), + next_watchpoint_id(0), + break_packet_dropped(false) {} + +void DebugDataManager::addCounter(std::string name) { + std::lock_guard guard(mutex_); + counters.insert(std::pair(name, 0)); +} + +void DebugDataManager::removeCounter(std::string name) { + std::lock_guard guard(mutex_); + counters.erase(name); +} + +void DebugDataManager::updateCounter(std::string name, int val) { + std::lock_guard guard(mutex_); + auto it = counters.find(name); + if (it != counters.end()) { + counters[name] = val; + } else { + // TODO(eric): Make this nicer + throw "COUNTER DOES NOT EXIST"; + } +} + +int DebugDataManager::getCounterValue(std::string name) { + std::lock_guard guard(mutex_); + auto it = counters.find(name); + if (it != counters.end()) { + return it->second; + } else { + return -1; + } +} + +std::map DebugDataManager::getCounters() { + std::lock_guard guard(mutex_); + return counters; +} + +void DebugDataManager::addBreakpoint(Breakpoint br) { + std::lock_guard guard(mutex_); + for (auto bkpt = breakpoint_list.begin(); + bkpt != breakpoint_list.end(); bkpt++) { + if (bkpt->isEqual(br)) { + return; + } + } + breakpoint_list.push_back(br); +} + +void DebugDataManager::removeBreakpoint(int identifier) { + std::lock_guard guard(mutex_); + for (auto bkpt = breakpoint_list.begin(); + bkpt != breakpoint_list.end(); bkpt++) { + if (bkpt->getID() == identifier) { + breakpoint_list.erase(bkpt); + break; + } + } +} + +void DebugDataManager::enableBreakpoint(int id) { + std::lock_guard guard(mutex_); + for (auto bkpt = breakpoint_list.begin(); + bkpt != breakpoint_list.end(); bkpt++) { + if (bkpt->getID() == id) { + bkpt->disabled = false; + break; + } + } +} + +void DebugDataManager::disableBreakpoint(int id) { + std::lock_guard guard(mutex_); + for (auto bkpt = breakpoint_list.begin(); + bkpt != breakpoint_list.end(); bkpt++) { + if (bkpt->getID() == id) { + bkpt->disabled = true; + break; + } + } +} + +void DebugDataManager::updatePacket(int id, std::string module, double time_, + bool read) { + std::lock_guard guard(mutex_); + auto check_pk = packet_list.find(id); + if (check_pk == packet_list.end()) { + DebuggerPacket pk(id, module, time_); + packet_list[id] = pk; + } + DebuggerPacket& packet = packet_list.at(id); + if (read) { + packet.setTime(time_); + packet.setCurrentLocation(module); + packet.updateTraceReadTime(module, time_); + } else { + packet.updateTraceWriteTime(module, time_); + } +} + +void DebugDataManager::removePacket(int id) { + std::lock_guard guard(mutex_); + packet_list.erase(id); +} + +void DebugDataManager::addWatchpoint(Watchpoint wp) { + std::lock_guard guard(mutex_); + for (auto it = watchpoint_list.begin(); it != watchpoint_list.end(); it++) { + if (it->getCounterName() == wp.getCounterName()) { + return; + } + } + watchpoint_list.push_back(wp); +} + +void DebugDataManager::removeWatchpoint(int id) { + std::lock_guard guard(mutex_); + for (auto it = watchpoint_list.begin(); it != watchpoint_list.end(); it++) { + if (it->getID() == id) { + watchpoint_list.erase(it); + break; + } + } +} + +void DebugDataManager::enableWatchpoint(int id) { + std::lock_guard guard(mutex_); + for (auto it = watchpoint_list.begin(); it != watchpoint_list.end(); it++) { + if (it->getID() == id) { + it->disabled = false; + break; + } + } +} + +void DebugDataManager::disableWatchpoint(int id) { + std::lock_guard guard(mutex_); + for (auto it = watchpoint_list.begin(); it != watchpoint_list.end(); it++) { + if (it->getID() == id) { + it->disabled = true; + break; + } + } +} + +int DebugDataManager::whoami() { + std::lock_guard guard(mutex_); + return current_packet_id; +} +void DebugDataManager::set_whoami(int id) { + std::lock_guard guard(mutex_); + current_packet_id = id; +} + +void DebugDataManager::addIgnoreModule(std::string mod) { + std::lock_guard guard(mutex_); + for (auto it = ignore_module_list.begin(); + it != ignore_module_list.end(); it++) { + if (mod == *it) { + return; + } + } + ignore_module_list.push_back(mod); +} + +bool DebugDataManager::checkIgnoreModules(std::string mod) { + std::lock_guard guard(mutex_); + for (auto it = ignore_module_list.begin(); + it != ignore_module_list.end(); it++) { + if (*it == mod) { + return true; + } + } + return false; +} + +void DebugDataManager::removeIgnoreModule(std::string mod) { + std::lock_guard guard(mutex_); + for (auto it = ignore_module_list.begin(); + it != ignore_module_list.end(); it++) { + if (*it == mod) { + ignore_module_list.erase(it); + break; + } + } +} + +void DebugDataManager::addDroppedPacket(int id, std::string module, + std::string reason) { + std::lock_guard guard(mutex_); + dropped_packet_list.push_back( + std::tuple(id, module, reason)); +} + +void DebugDataManager::setBreakOnPacketDrop(bool b) { + std::lock_guard guard(mutex_); + break_packet_dropped = b; +} + +bool DebugDataManager::getBreakOnPacketDrop() { + std::lock_guard guard(mutex_); + return break_packet_dropped; +} + +Breakpoint DebugDataManager::getBreakpoint(int index) { + std::lock_guard guard(mutex_); + return breakpoint_list[index]; +} + +std::vector& DebugDataManager::getBreakpointList() { + std::lock_guard guard(mutex_); + return breakpoint_list; +} + +int DebugDataManager::getNumberOfBreakpoints() { + std::lock_guard guard(mutex_); + return breakpoint_list.size(); +} + +double DebugDataManager::getSimulationTime() { + std::lock_guard guard(mutex_); + return simulation_time; +} + +DebuggerPacket* DebugDataManager::getPacket(int id) { + std::lock_guard guard(mutex_); + try { + return &packet_list.at(id); + } catch (std::out_of_range e) { + return NULL; + } +} + +std::map& DebugDataManager::getPacketList() { + std::lock_guard guard(mutex_); + return packet_list; +} + +std::vector& DebugDataManager::getWatchpointList() { + std::lock_guard guard(mutex_); + return watchpoint_list; +} + +std::vector& DebugDataManager::getIgnoreModuleList() { + std::lock_guard guard(mutex_); + return ignore_module_list; +} + +std::vector>& +DebugDataManager::getDroppedPacketList() { + std::lock_guard guard(mutex_); + return dropped_packet_list; +} + +void DebugDataManager::setSimulationTime(double time_ns) { + std::lock_guard guard(mutex_); + simulation_time = time_ns; +} + +}; // namespace db +}; // namespace core +}; // namespace pfp diff --git a/pfpsim/core/debugger/DebugDataManager.h b/pfpsim/core/debugger/DebugDataManager.h new file mode 100644 index 0000000..43d3e10 --- /dev/null +++ b/pfpsim/core/debugger/DebugDataManager.h @@ -0,0 +1,308 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** +* @file DebugDataManager.h +* Defines class which stores any data about taht simulation that is required by pfpdb. +* +* Created on: January 28, 2016 +* Author: Eric Tremblay +*/ + +#ifndef CORE_DEBUGGER_DEBUGDATAMANAGER_H_ +#define CORE_DEBUGGER_DEBUGDATAMANAGER_H_ + +#include +#include +#include +#include +#include +#include + +#include "Breakpoint.h" +#include "Watchpoint.h" +#include "DebuggerPacket.h" + +namespace pfp { +namespace core { +namespace db { + +/** + * Stores any data acquired from the simulation from the observer so that it may be fetched by the server and sent to the debugger. + */ +class DebugDataManager { + public: + /** + * Default Constructor + */ + DebugDataManager(); + + /** + * Add a counter. + * @param name Name of counter. + */ + void addCounter(std::string name); + + /** + * Remove a counter. + * @param name Name of counter. + */ + void removeCounter(std::string name); + + /** + * Update the value of a counter. + * @param name Name of counter. + * @param val New counter value. + */ + void updateCounter(std::string name, int val); + + /** + * Add a Breakpoint. + * @param br Breakpoint to add. + */ + void addBreakpoint(Breakpoint br); + + /** + * Remove a Breakpoint. + * @param identifier ID of Breakpoint to remove. + */ + void removeBreakpoint(int identifier); + + /** + * Enable a Breakpoint. Does nothing if it is already enabled. + * @param id ID of Breakpoint to enable. + */ + void enableBreakpoint(int id); + + /** + * Disable a Breakpoint. Does nothing if it is already disabled. + * @param id ID of Breakpoint to disable. + */ + void disableBreakpoint(int id); + + /** + * Add a new packet or update an existing one. + * @param id ID of packet. + * @param module Name of module the packet is currently in. + * @param time_ Time of update. + * @param read Indicates whether the update is for a read or a write. True = read, False = write. + */ + void updatePacket(int id, std::string module, double time_, bool read); + + /** + * Remove a packet. + * @param id ID of packet. + */ + void removePacket(int id); + + /** + * Add a Watchpoint. + * @param wp Watchpoint to add. + */ + void addWatchpoint(Watchpoint wp); + + /** + * Remove a Watchpoint. + * @param id ID of Watchpoint to remove. + */ + void removeWatchpoint(int id); + + /** + * Enable a Watchpoint. Does nothing if the Watchpoint is already enabled. + * @param id ID of Watchpoint to enable. + */ + void enableWatchpoint(int id); + + /** + * Disable a Watchpoint. Does nothing if the Watchpoint is already disabled. + * @param id ID of Watchpoint to disable. + */ + void disableWatchpoint(int id); + + /** + * Get the ID of the packet that is the debugger is currently following or focusing on. + * @return ID of packet. + */ + int whoami(); + + /** + * Set which packet is currently be followed or focused on. + * @param id ID of packet. + */ + void set_whoami(int id); + + /** + * Add a module to ignore. + * Ignored modules will not appear in the output of certain debugger commands and the debugger will not break on these modules. + * @param mod Name of module to ignore. + */ + void addIgnoreModule(std::string mod); + + /** + * Check if the given module is being ignored. + * @param mod Name of module to check. + * @return true if the module is ignored, false if it is not. + */ + bool checkIgnoreModules(std::string mod); + + /** + * Remove a module from the list of ignored modules. + * @param mod Name of module to stop ignoring. + */ + void removeIgnoreModule(std::string mod); + + /** + * Add a packet to the list of dropped packets. + * @param id ID of packet. + * @param module Module it was dropped in. + * @param reason Reason for which it was dropped. + */ + void addDroppedPacket(int id, std::string module, std::string reason); + + /** + * Set whether the debugger should break when a packet is dropped. + * @param b True will enable the breaking on drop packets. False will disable it. + */ + void setBreakOnPacketDrop(bool b); + + /** + * Check if the debugger is currently set to break when a packet is dropped. + * @return true if it is set to break, false if it is not. + */ + bool getBreakOnPacketDrop(); + + /** + * Get the value of a counter. + * @param name Name of counter. + * @return Current value of counter. + */ + int getCounterValue(std::string name); + + /** + * Get map of counters and their values. + * @return Map with counter name as the key and value of the counter as the value. + */ + std::map getCounters(); + + /** + * Get Breakpoint at given index from list of Breakpoints. + * @param index Index in the list. + * @return Breakpoint at given index. + */ + Breakpoint getBreakpoint(int index); + + /** + * Get list of Breakpoints. + * @return Vector of Breakpoints. + */ + std::vector& getBreakpointList(); + + /** + * Get number of Breakpoints in list. + * @return Number of Breakpoints. + */ + int getNumberOfBreakpoints(); + + /** + * Get current simulation time. + * @return Current simulation time. + */ + double getSimulationTime(); + + /** + * Get DebuggerPacket with given ID> + * @param id ID of desired packet. + * @return Packet with given ID. Returns NULL if it does not exist. + */ + DebuggerPacket* getPacket(int id); + + /** + * Get the map containing the DebuggerPacket as the key and the DebuggerPacket object as the value. + * @return Map of DebuggerPackets. + */ + std::map& getPacketList(); + + /** + * Get list of Watchpoints. + * @return Vector of Watchpoints. + */ + std::vector& getWatchpointList(); + + /** + * Get list of modules that are being ignored. + * @return Vector containing the names of the modules being ignored. + */ + std::vector& getIgnoreModuleList(); + + /** + * Get vector of tuple representing a dropped packet. The first element is the packet ID, the second element is the module in which the packet was dropped and the third element is the reason for which it was dropped. + * @return Vector of dropped packets. + */ + std::vector>& getDroppedPacketList(); + + /** + * Set the current simulation time. + * @param time_ns Current simulation time in nanoseconds. + */ + void setSimulationTime(double time_ns); + + private: + //! Map with counter name as key and counter value as the value. + std::map counters; + //! Mutex to make sure only one thread access the variables + //! of this class at a time. + std::mutex mutex_; + //! Map of packets in simulator with their id as the key. + std::map packet_list; + //! List of Breakpoint objects. + std::vector breakpoint_list; + //! simulation time in ns + double simulation_time; + //! whoami packet id + int current_packet_id; + //! List of Watchpoint objects. + std::vector watchpoint_list; + //! Next available Watchpoint ID. + int next_watchpoint_id; + //! list of modules to be ignored. + std::vector ignore_module_list; + //! List of packets that were dropped. + std::vector> dropped_packet_list; + //! Flag which indicates if the debugger should break when a packet + //! is dropped. + bool break_packet_dropped; +}; + +}; // namespace db +}; // namespace core +}; // namespace pfp + +#endif // CORE_DEBUGGER_DEBUGDATAMANAGER_H_ diff --git a/pfpsim/core/debugger/DebugObserver.cpp b/pfpsim/core/debugger/DebugObserver.cpp new file mode 100644 index 0000000..01984e0 --- /dev/null +++ b/pfpsim/core/debugger/DebugObserver.cpp @@ -0,0 +1,330 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "DebugObserver.h" +#include +#include +#include +#include "Breakpoint.h" +#include "Watchpoint.h" +#include "../PacketBase.h" + +namespace pfp { +namespace core { +namespace db { + +DebugObserver::DebugObserver() { + // create data manager + data_manager = new DebugDataManager(); + + // start ipc server + ipc_server + = new DebuggerIPCServer("ipc:///tmp/pfpsimdebug.ipc", data_manager); + ipc_server->start(); +} + +DebugObserver::~DebugObserver() { + while (enable) { + SimulationEndMessage *msg = new SimulationEndMessage(); + ipc_server->setReplyMessage(msg); + notifyServer(); + pause(); + } + delete ipc_server; +} + +void DebugObserver::waitForRunCommand() { + ipc_server->waitForRunCommand(); +} + +void DebugObserver::counter_added(const std::string& module_name, + const std::string& counter_name, double simulation_time) { + if (VERBOSE) { + std::cout << "DEBUGOBS: " << "Counter Added: " << module_name << ": " + << counter_name << " @ " << simulation_time << std::endl; + } + + data_manager->addCounter(counter_name); + updateSimulationTime(simulation_time); +} + +void DebugObserver::counter_removed(const std::string& module_name, + const std::string& counter_name, double simulation_time) { + if (VERBOSE) { + std::cout << "DEBUGOBS: " << "Counter Removed: " << module_name << ": " + << counter_name << " @ " << simulation_time << std::endl; + } + + data_manager->removeCounter(counter_name); + updateSimulationTime(simulation_time); +} + +void DebugObserver::counter_updated(const std::string& module_name, + const std::string& counter_name, + std::size_t new_value, + double simulation_time) { + if (VERBOSE) { + std::cout << "DEBUGOBS: " << "Counter Updated: " << module_name << ": " + << counter_name << ": " << new_value << " @ " + << simulation_time << std::endl; + } + int old_value = data_manager->getCounterValue(counter_name); + if (old_value == -1) { + old_value = 0; + } + data_manager->updateCounter(counter_name, static_cast(new_value)); + updateSimulationTime(simulation_time); + std::vector& watchpoints = data_manager->getWatchpointList(); + for (auto it = watchpoints.begin(); it != watchpoints.end(); it++) { + if (counter_name == it->getCounterName() && !it->disabled) { + WatchpointHitMessage *watchpoint_hit_msg + = new WatchpointHitMessage( + it->getID(), counter_name, old_value, new_value); + ipc_server->setReplyMessage(watchpoint_hit_msg); + notifyServer(); + pause(); + } + } +} + +void DebugObserver::data_written(const std::string& from_module, + const std::shared_ptr data, double simulation_time) { + if (!data->debuggable()) { + return; + } + if (VERBOSE) { + std::cout << "DEBUGOBS: " << "Data Written" << " @ " << simulation_time + << " from " << from_module << ":\nType: " << data->data_type() + << "\nPacket ID: " << data->id() << std::endl; + } + updateSimulationTime(simulation_time); + + data_manager->updatePacket(data->id(), from_module, simulation_time, false); + + if (!data_manager->checkIgnoreModules(from_module)) { + checkBreakpointHit(from_module, data->id(), simulation_time, false); + } +} + +void DebugObserver::data_read(const std::string& to_module, + const std::shared_ptr data, double simulation_time) { + if (!data->debuggable()) { + return; + } + + if (VERBOSE) { + std::cout << "DEBUGOBS: " << "Data Read" << " @ " + << std::fixed << simulation_time << " ns to " << to_module + << ":\nType: " << data->data_type() + << "\nPacket ID: " << data->id() << std::endl; + } + + updateSimulationTime(simulation_time); + updatePacketList(data->id(), to_module, simulation_time); + + if (!data_manager->checkIgnoreModules(to_module)) { + checkBreakpointHit(to_module, data->id(), simulation_time, true); + } +} + +void DebugObserver::data_dropped(const std::string& in_module, + const std::shared_ptr data, + const std::string& drop_reason, + double simulation_time) { + if (!data->debuggable()) { + return; + } + + if (VERBOSE) { + std::cout << "DEBUGOBS: " << "Data Dropped " << "@ " << simulation_time + << " in " << in_module << ":\nType: " << data->data_type() + << "\nPacket ID: " << data->id() << "\nSource: " << std::endl; + } + updateSimulationTime(simulation_time); + data_manager->addDroppedPacket(data->id(), in_module, drop_reason); + if (data_manager->getBreakOnPacketDrop()) { + PacketDroppedMessage *msg + = new PacketDroppedMessage(data->id(), in_module, drop_reason); + ipc_server->setReplyMessage(msg); + notifyServer(); + pause(); + } +} + +void DebugObserver::thread_begin(const std::string& teu_mod, + const std::string& tec_mod, std::size_t thread_id, + std::size_t packet_id, double simulation_time) { + if (VERBOSE) { + std::cout << "DEBUGOBS: " << "Thread Started: " << teu_mod << ": " + << tec_mod << ": " << thread_id << ": " << packet_id << " @ " + << simulation_time << std::endl; + } + updateSimulationTime(simulation_time); +} + +void DebugObserver::thread_end(const std::string& teu_mod, + const std::string& tec_mod, std::size_t thread_id, + std::size_t packet_id, double simulation_time) { + if (VERBOSE) { + std::cout << "DEBUGOBS: " << "Thread Ended: " << teu_mod << ": " + << tec_mod << ": " << thread_id << ": " << packet_id + << " @ " << simulation_time << std::endl; + } + updateSimulationTime(simulation_time); +} + +void DebugObserver::thread_idle(const std::string& teu_mod, + const std::string& tec_mod, std::size_t thread_id, + std::size_t packet_id, double simulation_time) { + if (VERBOSE) { + std::cout << "DEBUGOBS: " << "Thread Idle: " << teu_mod << ": " + << tec_mod << ": " << thread_id << ": " << packet_id + << " @ " << simulation_time << std::endl; + } + updateSimulationTime(simulation_time); +} + +void DebugObserver::core_busy(const std::string& teu_mod, + const std::string& tec_mod, double simulation_time) { + updateSimulationTime(simulation_time); +} + +void DebugObserver::core_idle(const std::string& teu_mod, + const std::string& tec_mod, double simulation_time) { + updateSimulationTime(simulation_time); +} + +void DebugObserver::enableDebugger() { + enable = true; +} + +void DebugObserver::pause() { + std::mutex& m = ipc_server->getBkptMutex(); + std::condition_variable& cv = ipc_server->getBkptConditionVariable(); + std::unique_lock lock(m); + while (!ipc_server->getContinueCommand()) { + cv.wait(lock); + } + ipc_server->setContinueCommand(false); + lock.unlock(); +} + +void DebugObserver::notifyServer() { + std::mutex& m = ipc_server->getStopMutex(); + std::condition_variable& cv = ipc_server->getStopConditionVariable(); + std::unique_lock lock(m); + ipc_server->setStopped(true); + lock.unlock(); + cv.notify_all(); +} + +void DebugObserver::updateSimulationTime(double sim_time) { + if (sim_time > data_manager->getSimulationTime()) { + data_manager->setSimulationTime(sim_time); + } +} + +void DebugObserver::updateWhoAmI(int packet_id) { + data_manager->set_whoami(packet_id); +} + +void DebugObserver::updatePacketList(int id, std::string loc, double t) { + data_manager->updatePacket(id, loc, t, true); +} + +void DebugObserver::checkBreakpointHit(std::string module, int packet_id, + double sim_time, bool read) { + bool break_hit = true; + std::vector& breakpoints = data_manager->getBreakpointList(); + if (breakpoints.size() == 0) { + break_hit = false; + } + Breakpoint *hit_bkpt = NULL; + for (auto bkpt = breakpoints.begin(); bkpt != breakpoints.end(); bkpt++) { + for (auto cond = bkpt->conditions.begin(); + cond != bkpt->conditions.end(); cond++) { + if (cond->first == Breakpoint::BreakpointCondition::BREAK_ON_MODULE_READ + && (!read || cond->second != module)) { + break_hit = false; + break; + } else if (cond->first + == Breakpoint::BreakpointCondition::BREAK_ON_MODULE_WRITE + && (read || cond->second != module)) { + break_hit = false; + break; + } else if (cond->first + == Breakpoint::BreakpointCondition::BREAK_ON_PACKET_ID + && cond->second != std::to_string(packet_id)) { + break_hit = false; + break; + } else if (cond->first + == Breakpoint::BreakpointCondition::BREAK_AT_TIME + && stof(cond->second) > sim_time) { + break_hit = false; + break; + } + } + if (break_hit == true) { + hit_bkpt = &(*bkpt); + break; + } else { + break_hit = true; + } + } + + if (hit_bkpt && hit_bkpt->disabled == false) { + updateWhoAmI(packet_id); + // Check if its a stealth breakpoint + if (hit_bkpt->getID() != -1) { + BreakpointHitMessage *bkpt_hit_msg = new BreakpointHitMessage( + hit_bkpt->getID(), module, packet_id, sim_time, read); + ipc_server->setReplyMessage(bkpt_hit_msg); + data_manager->removeBreakpoint(-1); + } else { + SimulationStoppedMessage *sim_stopped_msg + = new SimulationStoppedMessage(module, packet_id, sim_time, read); + ipc_server->setReplyMessage(sim_stopped_msg); + } + // remove bkpt from list if temporary + if (hit_bkpt->temp == true) { + data_manager->removeBreakpoint(hit_bkpt->getID()); + } + notifyServer(); + pause(); + } +} + +void DebugObserver::registerCP(CPDebuggerInterface *cp_debug_if) { + ipc_server->registerCP(cp_debug_if); +} + +}; // namespace db +}; // namespace core +}; // namespace pfp diff --git a/pfpsim/core/debugger/DebugObserver.h b/pfpsim/core/debugger/DebugObserver.h new file mode 100644 index 0000000..7e24e91 --- /dev/null +++ b/pfpsim/core/debugger/DebugObserver.h @@ -0,0 +1,241 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** +* @file DebugObserver.h +* Observer for simulation which maintains a record of the simulation via the DebugDataManager so that the debugger may get the necessary information. +* +* Created on: January 22, 2016 +* Author: Eric Tremblay +*/ + +#ifndef CORE_DEBUGGER_DEBUGOBSERVER_H_ +#define CORE_DEBUGGER_DEBUGOBSERVER_H_ + +#include +#include +#include "../TrType.h" +#include "../PFPObserver.h" +#include "DebuggerIPCServer.h" +#include "DebugDataManager.h" +#include "DebuggerPacket.h" + +#define VERBOSE 0 + +namespace pfp { +namespace core { +namespace db { + +/** +* Observer for pfpdb. +* The observer gets notified of any events within the simulation and is responsible for maintaining relevant information for the debugger. +* This object should only be created if the user compiles the PFPSim with the appropriate debug flag. +* It is the user's responsibility to notify this observer appropriately. +*/ +class DebugObserver : public PFPObserver { + public: + /** + * Default Constructor. + */ + DebugObserver(); + + /** + * Destructor. + */ + ~DebugObserver(); + + /** + * Function called by the simulation when a counter is added to a module + * @param module_name Module name to which counter was added + * @param counter_name Name of the counter + * @param simulation_time Simulation time at which counter was added (defaults to 0 since counters are typically added before simulation begins) + */ + virtual void counter_added(const std::string& module_name, + const std::string& counter_name, double simulation_time = 0); + + /** + * Function called by the simulation when a counter is removed from a module + * @param module_name Module name from which counter was removed + * @param counter_name Name of the counter + * @param simulation_time Simulation time at which counter was removed (defaults to 0 since counters are typically removed before simulation begins) + */ + virtual void counter_removed(const std::string& module_name, + const std::string& counter_name, double simulation_time = 0); + + /** + * Function called by the simulation when a counter is updated + * @param module_name Module containing updated counter + * @param counter_name Name of the counter + * @param new_value Current value of the counter + * @param simulation_time Simulation time at which counter value was updated + */ + virtual void counter_updated(const std::string& module_name, + const std::string& counter_name, + std::size_t new_value, + double simulation_time); + + /** + * Function called by the simulation when data is written by a module + * @param from_module Module name of the transmitting module + * @param data JSON representation of data + * @param simulation_time Simulation time at which event occurred + */ + void data_written(const std::string& from_module, + const std::shared_ptr data, + double simulation_time) override; + + /** + * Function called by the simulation when data is read by a module + * @param to_module Module name of the receiving module + * @param data JSON representation of data + * @param simulation_time Simulation time at which event occurred + */ + void data_read(const std::string& to_module, + const std::shared_ptr data, + double simulation_time) override; + + /** + * Function called by the simulation when data is dropped in a module + * @param in_module Module name in which data was dropped + * @param data JSON representation of data + * @param simulation_time Simulation time at which event occurred + */ + void data_dropped(const std::string& in_module, + const std::shared_ptr data, + const std::string& drop_reason, + double simulation_time) override; + + /** + * Function called by the simulation when a TEU thread begins + * @param teu_mod TEU in which thread was started + * @param tec_mod TEC containing the TEU in which the thread was started + * @param thread_id ID number of the thread + * @param simulation_time Simulation time at which event occurred + */ + virtual void thread_begin(const std::string& teu_mod, + const std::string& tec_mod, std::size_t thread_id, + std::size_t packet_id, double simulation_time); + + /** + * Function called by the simulation when a TEU thread ends + * @param teu_mod TEU in which thread was ended + * @param tec_mod TEC containing the TEU in which the thread was ended + * @param thread_id ID number of the thread + * @param simulation_time Simulation time at which event occurred + */ + virtual void thread_end(const std::string& teu_mod, + const std::string& tec_mod, std::size_t thread_id, + std::size_t packet_id, double simulation_time); + + /** + * Function called by the simulation when a TEU thread starts idling + * @param teu_mod TEU in which thread is idling + * @param tec_mod TEC containing the TEU in which the thread is idling + * @param thread_id ID number of the thread + * @param simulation_time Simulation time at which event occurred + */ + virtual void thread_idle(const std::string& teu_mod, + const std::string& tec_mod, std::size_t thread_id, + std::size_t packet_id, double simulation_time); + + virtual void core_busy(const std::string& teu_mod, + const std::string& tec_mod, double simulation_time); + virtual void core_idle(const std::string& teu_mod, + const std::string& tec_mod, double simulation_time); + + /** + * Called after SystemC elaboration to halt the simulation until the run command is sent. + */ + void waitForRunCommand(); + + /** + * Register a Control Plane (which implements CPDebuggerInterface) to the debugger. + * This gives the debugger the ability to insert, modify and delete table entries as well as obtain the state of the tables. + * @param cp_debug_if Control Plane object. + */ + void registerCP(CPDebuggerInterface *cp_debug_if); + + /** + * Enables the debugger. + */ + void enableDebugger(); + + private: + DebuggerIPCServer *ipc_server; /*!< Pointer to IPCServer. */ + DebugDataManager *data_manager; /*!< Pointer to DebugDataManager. */ + bool enable = false; /*!< Indicates if the debugger is enabled. */ + + /** + * Function called to block the simulation from within the observer + */ + void pause(); + + /** + * Function called to notify the server that the simulation has stopped and that it can now give back control to the debugger + */ + void notifyServer(); + + /** + * Update the current simulation time to a more recent value. + * @param sim_time New simulation time in nanoseconds. + */ + void updateSimulationTime(double sim_time); + + /** + * Update which packet is currently be followed or focused on. + * @param packet_id ID of packet. + */ + void updateWhoAmI(int packet_id); + + /** + * Update the list of packets. + * This adds the packet if does not exists. + * @param id ID of packet. + * @param loc Module the packet is currently in. + * @param t Current simulation time in nanoseconds. + */ + void updatePacketList(int id, std::string loc, double t); + + /** + * Check to see if the module, packet id or simulation time should cause a breakpoint hit. + * @param module Current module name. + * @param packet_id ID of current packet. + * @param sim_time Current simulation time in nanoseconds. + * @param read Indicates if the packet is entering or exiting the module.If it's not a read than it's a write. + */ + void checkBreakpointHit(std::string module, int packet_id, + double sim_time, bool read); +}; + +}; // namespace db +}; // namespace core +}; // namespace pfp + +#endif // CORE_DEBUGGER_DEBUGOBSERVER_H_ diff --git a/pfpsim/core/debugger/DebuggerIPCServer.cpp b/pfpsim/core/debugger/DebuggerIPCServer.cpp new file mode 100644 index 0000000..a423d7a --- /dev/null +++ b/pfpsim/core/debugger/DebuggerIPCServer.cpp @@ -0,0 +1,669 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "DebuggerIPCServer.h" +#include +#include +#include +#include +#include +#include "Breakpoint.h" +#include "Watchpoint.h" +#include "DebuggerPacket.h" + +namespace pfp { +namespace core { +namespace db { + +DebuggerIPCServer::DebuggerIPCServer(std::string url, DebugDataManager *dm) + : data_manager(dm), reply_message(NULL) { + socket = nn_socket(AF_SP, NN_REP); + nn_bind(socket, url.c_str()); +} + +DebuggerIPCServer::~DebuggerIPCServer() { + delete ulock; + kill_thread = true; + nn_shutdown(socket, 0); + debug_req_thread.join(); +} + +void DebuggerIPCServer::start() { + ulock = new std::unique_lock(start_mutex); + debug_req_thread = std::thread(&DebuggerIPCServer::requestThread, this); +} + +void DebuggerIPCServer::waitForRunCommand() { + std::lock_guard guard(start_mutex); +} + +std::mutex& DebuggerIPCServer::getBkptMutex() { + return bkpt_mutex; +} + +std::mutex& DebuggerIPCServer::getStopMutex() { + return stop_mutex; +} + +std::condition_variable& DebuggerIPCServer::getBkptConditionVariable() { + return bkpt_cv; +} + +std::condition_variable& DebuggerIPCServer::getStopConditionVariable() { + return stop_cv; +} + +bool DebuggerIPCServer::getContinueCommand() const { + return continue_command; +} + +void DebuggerIPCServer::setContinueCommand(bool b) { + continue_command = b; +} + +bool DebuggerIPCServer::getStopped() const { + return stopped; +} + +void DebuggerIPCServer::setStopped(bool b) { + stopped = b; +} + +void DebuggerIPCServer::setReplyMessage(DebuggerMessage *message) { + reply_message = message; +} + +void DebuggerIPCServer::registerCP(CPDebuggerInterface *cp_debug_if) { + control_plane = cp_debug_if; +} + +void DebuggerIPCServer::send(DebuggerMessage *message) { + std::string message_string; + message->SerializeToString(&message_string); + int bytes_sent = nn_send(socket, message_string.c_str(), + message_string.size(), 0); +} + +PFPSimDebugger::DebugMsg* DebuggerIPCServer::recv() { + struct nn_pollfd pfd; + pfd.fd = socket; + pfd.events = NN_POLLIN; + + auto rc = nn_poll(&pfd, 1, 100); + if (rc == 0) { + return nullptr; + } + + if (rc == -1) { + // TODO(gordon) + printf("Error!"); + exit(1); + } + + if (pfd.revents & NN_POLLIN) { + char *buf; + int bytes = nn_recv(socket, &buf, NN_MSG, 0); + if (bytes != -1) { + std::string message_string(buf); + nn_freemsg(buf); + + PFPSimDebugger::DebugMsg *message = new PFPSimDebugger::DebugMsg(); + message->ParseFromString(message_string); + return message; + } else { + return nullptr; + } + } else { + return nullptr; + } +} + +void DebuggerIPCServer::parseRequest(PFPSimDebugger::DebugMsg *request) { + switch (request->type()) { + case PFPSimDebugger::DebugMsg_Type_Run: + { + if (!run_called) { + run_called = true; + PFPSimDebugger::RunMsg msg; + msg.ParseFromString(request->message()); + if (msg.has_time_ns()) { + handleRun(stod(msg.time_ns())); + } else { + handleRun(); + } + } else { + // TODO(eric): this is a temporary fix for the double run message problem + SimulationEndMessage *msg = new SimulationEndMessage(); + send(msg); + delete msg; + } + break; + } + case PFPSimDebugger::DebugMsg_Type_GetCounter: + { + PFPSimDebugger::GetCounterMsg msg; + msg.ParseFromString(request->message()); + handleGetCounter(msg); + break; + } + case PFPSimDebugger::DebugMsg_Type_GetAllCounters: + { + handleGetAllCounters(); + break; + } + case PFPSimDebugger::DebugMsg_Type_SetBreakpoint: + { + PFPSimDebugger::SetBreakpointMsg msg; + msg.ParseFromString(request->message()); + handleSetBreakpoint(msg); + break; + } + case PFPSimDebugger::DebugMsg_Type_Continue: + { + PFPSimDebugger::ContinueMsg msg; + msg.ParseFromString(request->message()); + if (msg.has_time_ns()) { + handleContinue(stod(msg.time_ns())); // alternative to stod? + } else { + handleContinue(); + } + break; + } + case PFPSimDebugger::DebugMsg_Type_GetAllBreakpoints: + { + handleGetAllBreakpoints(); + break; + } + case PFPSimDebugger::DebugMsg_Type_RemoveBreakpoint: + { + PFPSimDebugger::RemoveBreakpointMsg msg; + msg.ParseFromString(request->message()); + handleRemoveBreakpoint(msg); + break; + } + case PFPSimDebugger::DebugMsg_Type_WhoAmI: + { + handleWhoAmI(); + break; + } + case PFPSimDebugger::DebugMsg_Type_Next: + { + handleNext(); + break; + } + case PFPSimDebugger::DebugMsg_Type_GetPacketList: + { + PFPSimDebugger::GetPacketListMsg msg; + msg.ParseFromString(request->message()); + handleGetPacketList(msg); + break; + } + case PFPSimDebugger::DebugMsg_Type_SetWatchpoint: + { + PFPSimDebugger::SetWatchpointMsg msg; + msg.ParseFromString(request->message()); + handleSetWatchpoint(msg); + break; + } + case PFPSimDebugger::DebugMsg_Type_GetAllWatchpoints: + { + handleGetAllWatchpoints(); + break; + } + case PFPSimDebugger::DebugMsg_Type_RemoveWatchpoint: + { + PFPSimDebugger::RemoveWatchpointMsg msg; + msg.ParseFromString(request->message()); + handleRemoveWatchpoint(msg); + break; + } + case PFPSimDebugger::DebugMsg_Type_Backtrace: + { + PFPSimDebugger::BacktraceMsg msg; + msg.ParseFromString(request->message()); + handleBacktrace(msg); + break; + } + case PFPSimDebugger::DebugMsg_Type_EnableDisableBreakpoint: + { + PFPSimDebugger::EnableDisableBreakpointMsg msg; + msg.ParseFromString(request->message()); + handleEnableDisableBreakpoint(msg); + break; + } + case PFPSimDebugger::DebugMsg_Type_EnableDisableWatchpoint: + { + PFPSimDebugger::EnableDisableWatchpointMsg msg; + msg.ParseFromString(request->message()); + handleEnableDisableWatchpoint(msg); + break; + } + case PFPSimDebugger::DebugMsg_Type_IgnoreModule: + { + PFPSimDebugger::IgnoreModuleMsg msg; + msg.ParseFromString(request->message()); + handleIgnoreModule(msg); + break; + } + case PFPSimDebugger::DebugMsg_Type_GetAllIgnoreModules: + { + handleGetAllIgnoreModules(); + break; + } + case PFPSimDebugger::DebugMsg_Type_GetSimulationTime: + { + handleGetSimulationTime(); + break; + } + case PFPSimDebugger::DebugMsg_Type_BreakOnPacketDrop: + { + PFPSimDebugger::BreakOnPacketDropMsg msg; + msg.ParseFromString(request->message()); + handleBreakOnPacketDrop(msg); + break; + } + case PFPSimDebugger::DebugMsg_Type_GetDroppedPackets: + { + handleGetDroppedPackets(); + break; + } + case PFPSimDebugger::DebugMsg_Type_CPCommand: + { + PFPSimDebugger::CPCommandMsg msg; + msg.ParseFromString(request->message()); + handleCPCommand(msg); + break; + } + case PFPSimDebugger::DebugMsg_Type_GetTableEntries: + { + handleGetTableEntries(); + break; + } + default: { + sendRequestFailed(); + } + } +} + +void DebuggerIPCServer::requestThread() { + while (!kill_thread) { + // non-blocking (timeout after 100ms) + PFPSimDebugger::DebugMsg *request = recv(); + if (request) { + // std::cout << "Msg Received!" << std::endl; + parseRequest(request); + delete request; + } + } +} + +void DebuggerIPCServer::waitForStop() { + std::unique_lock lock(stop_mutex); + while (!stopped) { + stop_cv.wait(lock); + } + stopped = false; + lock.unlock(); +} + +void DebuggerIPCServer::handleRun(double time_ns) { + if (time_ns != -1) { + double current_sim_time = data_manager->getSimulationTime(); + double break_time = current_sim_time + time_ns; + Breakpoint bkpt(true); + bkpt.temp = true; + bkpt.addCondition(Breakpoint::BreakpointCondition::BREAK_AT_TIME, + std::to_string(break_time)); + data_manager->addBreakpoint(bkpt); + } + ulock->unlock(); // release mutex so that program can start + waitForStop(); + DebuggerMessage *reply; + if (reply_message == NULL) { + reply = new GenericAcknowledgeMessage( + PFPSimDebugger::GenericAcknowledgeMsg_Status_SUCCESS); + } else { + reply = reply_message; + } + send(reply); + delete reply; +} + +void DebuggerIPCServer::handleContinue(double time_ns) { + if (time_ns != -1) { + double current_sim_time = data_manager->getSimulationTime(); + double break_time = current_sim_time + time_ns; + Breakpoint bkpt(true); + bkpt.temp = true; + bkpt.addCondition(Breakpoint::BreakpointCondition::BREAK_AT_TIME, + std::to_string(break_time)); + data_manager->addBreakpoint(bkpt); + } + std::unique_lock lock(bkpt_mutex); + continue_command = true; + lock.unlock(); + bkpt_cv.notify_all(); + waitForStop(); + DebuggerMessage *reply; + if (reply_message == NULL) { + reply = new GenericAcknowledgeMessage( + PFPSimDebugger::GenericAcknowledgeMsg_Status_SUCCESS); + } else { + reply = reply_message; + } + send(reply); + delete reply; +} + +void DebuggerIPCServer::handleGetAllCounters() { + std::map counters = data_manager->getCounters(); + const int num = counters.size(); + std::vector names(num); + std::vector values(num); + int i = 0; + for (auto it = counters.begin(); it != counters.end(); it++) { + names[i] = it->first; + values[i] = it->second; + i++; + } + AllCounterValuesMessage *message + = new AllCounterValuesMessage(names.data(), values.data(), num); + send(message); + delete message; +} + +void DebuggerIPCServer::handleGetCounter(PFPSimDebugger::GetCounterMsg msg) { + std::string name = msg.name(); + int value = data_manager->getCounterValue(name); + CounterValueMessage *message = new CounterValueMessage(name, value); + send(message); + delete message; +} + +void DebuggerIPCServer::handleSetBreakpoint( + PFPSimDebugger::SetBreakpointMsg msg) { + int size = msg.condition_list_size(); + Breakpoint br; + if (msg.temporary() == "1") { + br.temp = true; + } + if (msg.disabled() == "1") { + br.disabled = true; + } for (int i = 0; i < size; i++) { + PFPSimDebugger::BreakpointCondition condition = msg.condition_list(i); + std::string value = msg.value_list(i); + if (condition + == PFPSimDebugger::BreakpointCondition::BREAK_ON_MODULE_READ) { + br.addCondition( + Breakpoint::BreakpointCondition::BREAK_ON_MODULE_READ, value); + } else if (condition + == PFPSimDebugger::BreakpointCondition::BREAK_ON_MODULE_WRITE) { + br.addCondition( + Breakpoint::BreakpointCondition::BREAK_ON_MODULE_WRITE, value); + } else if (condition + == PFPSimDebugger::BreakpointCondition::BREAK_ON_PACKET_ID) { + br.addCondition( + Breakpoint::BreakpointCondition::BREAK_ON_PACKET_ID, value); + } else if (condition + == PFPSimDebugger::BreakpointCondition::BREAK_AT_TIME) { + br.addCondition(Breakpoint::BreakpointCondition::BREAK_AT_TIME, value); + br.temp = true; + } + } + data_manager->addBreakpoint(br); + sendGenericReply(); +} + +void DebuggerIPCServer::handleGetAllBreakpoints() { + std::vector breakpoints = data_manager->getBreakpointList(); + AllBreakpointValuesMessage *message + = new AllBreakpointValuesMessage(breakpoints); + send(message); + delete message; +} + +void DebuggerIPCServer::handleRemoveBreakpoint( + PFPSimDebugger::RemoveBreakpointMsg msg) { + int id = stoi(msg.id()); + data_manager->removeBreakpoint(id); + sendGenericReply(); +} + +void DebuggerIPCServer::handleWhoAmI() { + int id = data_manager->whoami(); + WhoAmIReplyMessage *message = new WhoAmIReplyMessage(id); + send(message); + delete message; +} + +void DebuggerIPCServer::handleNext() { + int id = data_manager->whoami(); + Breakpoint bkpt(true); + bkpt.addCondition(Breakpoint::BreakpointCondition::BREAK_ON_PACKET_ID, + std::to_string(id)); + bkpt.temp = true; + data_manager->addBreakpoint(bkpt); + // continue simulation + handleContinue(-1); +} + +void DebuggerIPCServer::handleGetPacketList( + PFPSimDebugger::GetPacketListMsg msg) { + std::string module = ""; + if (msg.has_module()) { + module = msg.module(); + } + std::map packets = data_manager->getPacketList(); + std::vector ids; + std::vector locations; + std::vector times; + int counter = 0; + for (auto it = packets.begin(); it != packets.end(); it++) { + DebuggerPacket pk = it->second; + if ((module == "" || pk.getLocation() == module) + && !data_manager->checkIgnoreModules(pk.getLocation())) { + ids.push_back(pk.getID()); + locations.push_back(pk.getLocation()); + times.push_back(pk.getTime()); + counter++; + } + } + PacketListValuesMessage *message = new PacketListValuesMessage(ids.data(), + locations.data(), times.data(), counter); + send(message); + delete message; +} + +void DebuggerIPCServer::handleSetWatchpoint( + PFPSimDebugger::SetWatchpointMsg msg) { + std::string counter_name = msg.counter_name(); + bool disabled = false; + if (msg.disabled() == "1") { + disabled = true; + } + Watchpoint wp(counter_name, disabled); + data_manager->addWatchpoint(wp); + sendGenericReply(); +} + +void DebuggerIPCServer::handleGetAllWatchpoints() { + std::vector& watchpoints = data_manager->getWatchpointList(); + AllWatchpointValuesMessage *message + = new AllWatchpointValuesMessage(watchpoints); + send(message); + delete message; +} + +void DebuggerIPCServer::handleRemoveWatchpoint( + PFPSimDebugger::RemoveWatchpointMsg msg) { + int id = stoi(msg.id()); + data_manager->removeWatchpoint(id); + sendGenericReply(); +} + +void DebuggerIPCServer::handleBacktrace(PFPSimDebugger::BacktraceMsg msg) { + int id; + if (msg.has_packet_id()) { + try { + id = stoi(msg.packet_id()); + } catch (const std::exception& ex) { + std::cout << ex.what() << std::endl; + sendRequestFailed(); + } + } else { + id = data_manager->whoami(); + } + DebuggerPacket *pk = data_manager->getPacket(id); + if (pk != NULL) { + std::vector trace = pk->getTrace(); + const int size = trace.size(); + std::vector modules(size); + std::vector read_times(size); + std::vector write_times(size); + for (int i = 0; i < size; i++) { + modules[i] = trace[i].module; + read_times[i] = trace[i].read_time; + write_times[i] = trace[i].write_time; + } + BacktraceReplyMessage *message = new BacktraceReplyMessage(id, + modules.data(), read_times.data(), write_times.data(), size); + send(message); + delete message; + } else { + sendRequestFailed(); + } +} + +void DebuggerIPCServer::handleEnableDisableBreakpoint( + PFPSimDebugger::EnableDisableBreakpointMsg msg) { + int id = stoi(msg.id()); + if (msg.enable() == "0") { + data_manager->disableBreakpoint(id); + } else { + data_manager->enableBreakpoint(id); + } + sendGenericReply(); +} + +void DebuggerIPCServer::handleEnableDisableWatchpoint( + PFPSimDebugger::EnableDisableWatchpointMsg msg) { + int id = stoi(msg.id()); + if (msg.enable() == "0") { + data_manager->disableWatchpoint(id); + } else { + data_manager->enableWatchpoint(id); + } + sendGenericReply(); +} + +void DebuggerIPCServer::handleIgnoreModule( + PFPSimDebugger::IgnoreModuleMsg msg) { + std::string module = msg.module(); + bool del = msg.delete_(); + if (del == true) { + data_manager->removeIgnoreModule(module); + } else { + data_manager->addIgnoreModule(module); + } + sendGenericReply(); +} + +void DebuggerIPCServer::handleGetAllIgnoreModules() { + std::vector &ignore_module_list + = data_manager->getIgnoreModuleList(); + AllIgnoreModulesMessage *message = new AllIgnoreModulesMessage( + ignore_module_list.data(), ignore_module_list.size()); + send(message); + delete message; +} + +void DebuggerIPCServer::handleGetSimulationTime() { + double sim_time = data_manager->getSimulationTime(); + SimulationTimeMessage *message = new SimulationTimeMessage(sim_time); + send(message); + delete message; +} + +void DebuggerIPCServer::handleBreakOnPacketDrop( + PFPSimDebugger::BreakOnPacketDropMsg msg) { + bool on = msg.on(); + data_manager->setBreakOnPacketDrop(on); + sendGenericReply(); +} + +void DebuggerIPCServer::handleGetDroppedPackets() { + std::vector> &dropped_packets + = data_manager->getDroppedPacketList(); + const int num = dropped_packets.size(); + std::vector ids; + std::vector modules; + std::vector reasons; + for (auto dp = dropped_packets.begin(); dp != dropped_packets.end(); dp++) { + ids.push_back(std::get<0>(*dp)); + modules.push_back(std::get<1>(*dp)); + reasons.push_back(std::get<2>(*dp)); + } + DroppedPacketsMessage *message = new DroppedPacketsMessage( + ids.data(), modules.data(), reasons.data(), num); + send(message); + delete message; +} + +void DebuggerIPCServer::handleCPCommand(PFPSimDebugger::CPCommandMsg msg) { + std::string command = msg.command(); + control_plane->do_command(command); + sendGenericReply(); +} + +void DebuggerIPCServer::handleGetTableEntries() { + std::vector entries + = control_plane->getAllTableEntries(); + TableEntriesMessage *message = new TableEntriesMessage(entries); + send(message); + delete message; +} + +void DebuggerIPCServer::sendGenericReply() { + GenericAcknowledgeMessage *message = new GenericAcknowledgeMessage( + PFPSimDebugger::GenericAcknowledgeMsg_Status_SUCCESS); + send(message); + delete message; +} + +void DebuggerIPCServer::sendRequestFailed() { + GenericAcknowledgeMessage *message = new GenericAcknowledgeMessage( + PFPSimDebugger::GenericAcknowledgeMsg_Status_FAILED); + send(message); + delete message; +} + +}; // namespace db +}; // namespace core +}; // namespace pfp diff --git a/pfpsim/core/debugger/DebuggerIPCServer.h b/pfpsim/core/debugger/DebuggerIPCServer.h new file mode 100644 index 0000000..6e0ce4c --- /dev/null +++ b/pfpsim/core/debugger/DebuggerIPCServer.h @@ -0,0 +1,254 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** +* @file DebuggerIPCServer.h +* Defines class that services requests from pfpdb. +* +* Created on: January 27, 2016 +* Author: Eric Tremblay +*/ + +#ifndef CORE_DEBUGGER_DEBUGGERIPCSERVER_H_ +#define CORE_DEBUGGER_DEBUGGERIPCSERVER_H_ +#include +#include +#include +#include +#include +#include +#include +#include "DebuggerMessages.h" +#include "DebugDataManager.h" +#include "CPDebuggerInterface.h" +#include "../proto/PFPSimDebugger.pb.h" + +namespace pfp { +namespace core { +namespace db { + +/** + * Establishes communication with pfpdb python script and services its requests. + * The inter-process communication is accomplished via messages send using nanomsg. + */ +class DebuggerIPCServer { + public: + /** + * Constructor. + * @param url URL for IPC communication. + * @param dm Pointer to the DebugDataManager. + */ + DebuggerIPCServer(std::string url, DebugDataManager *dm); + + /** + * Destructor. + */ + ~DebuggerIPCServer(); + + /** + * Starts the thread which services the requests from pfpdb. + */ + void start(); + + /** + * Called via the DebugObserver after SystemC elaboration to halt the simulation until the run command is sent. + */ + void waitForRunCommand(); + + /** + * Get reference to mutex used to halt simulation when a breakpoint is hit. + * @return Reference to mutex. + */ + std::mutex& getBkptMutex(); + + /** + * Get reference to mutex used to halt DebuggerIPCServer while the simulation is running. + * @return Reference to mutex. + */ + std::mutex& getStopMutex(); + + /** + * Get reference to condition variable used to notify the DebugObserver that a continue or next command has been received and thus th simulation may proceed. + * @return Reference to condition variable. + */ + std::condition_variable& getBkptConditionVariable(); + + /** + * Get reference to condition variable used by the DebugObserver to notify the DebuggerIPCServer that the simulation has halted. + * @return Reference to condition variable + */ + std::condition_variable& getStopConditionVariable(); + + /** + * Get whether a continue command has been received. + * @return True when a continue has been recevied, otherwise, false. + */ + bool getContinueCommand() const; + + /** + * Set the flag indicating if the continue command has been received. + * @param b New value of flag. + */ + void setContinueCommand(bool b); + + /** + * Get whether the simulation has stopped. + * @return True if the simulation is stopped, otherwise, false. + */ + bool getStopped() const; + + /** + * Set flag that indicates if the simulation is stopped. + * @param b New value of flag. + */ + void setStopped(bool b); + + /** + * Set the message that will be send to pfpdb the next time the simulation is halted. + * @param message Message object to send. + */ + void setReplyMessage(DebuggerMessage *message); + + /** + * Called via the DebugObserver to register a Control Plane module to the debugger so that pfpdb may interact with the Control Plane to insert, modify and delete table entries as well as obtain the current state of the tables. + * @param cp_debug_if Control Plane object. + */ + void registerCP(CPDebuggerInterface *cp_debug_if); + + private: + //! socket which binds to ipc url + int socket; + //! thread which services requests from debugger + std::thread debug_req_thread; + //! Indicates when the server thread should terminate + bool kill_thread = false; + //! pointer to data_manager object + DebugDataManager *data_manager; + //! mutex that blocks the simulation before sc_start + std::mutex start_mutex; + //! mutex that blocks the simulation when a breakpoint is hit + std::mutex bkpt_mutex; + //! mutex that blocks the ipc server thread and + //! thus the PFPSimDebugger when the simulation is running + std::mutex stop_mutex; + //! lock used on start_mutex + std::unique_lock *ulock; + //! condition variable to notify the simulation when it should unblock + //! after a breakpoint hit + std::condition_variable bkpt_cv; + //! condition variable to notify the ipc server thread when + //! the simulation has stopped + std::condition_variable stop_cv; + //! indicates when a continue command is sent + bool continue_command = false; + //! indicates when the simulation has stopped due to a breakpoint hit + bool stopped = false; + //! Indicates if the run command has been called or not. + bool run_called = false; + + //! pointer to message that should be sent when the simulation stops + DebuggerMessage *reply_message; + + //! Pointer to registered CPDebuggerInterface object. + CPDebuggerInterface *control_plane; + + /** + * Send a DebuggerMessage to pfpdb. + * @param message Message to send. + */ + void send(DebuggerMessage *message); + + /** + * Receive a request message from pfpdb. + * @return Request from pfpdb. + */ + PFPSimDebugger::DebugMsg* recv(); + + /** + * Parse the request from pfpdb and call the appropriate function to handle the particular request. + * @param request Message from pfpdb. + */ + void parseRequest(PFPSimDebugger::DebugMsg *request); + + /** + * Thread which listens on the IPC channel and services any request from the debugger. + */ + void requestThread(); + + /** + * Halts the DebuggerIPCServer until the simulation stops, where control is given back to pfpdb. + */ + void waitForStop(); + + /** + * Member functions used to handle each type of message that pfpdb can send. + */ + void handleRun(double time_ns = -1); + void handleContinue(double time_ns = -1); + void handleGetAllCounters(); + void handleGetCounter(PFPSimDebugger::GetCounterMsg msg); + void handleSetBreakpoint(PFPSimDebugger::SetBreakpointMsg msg); + void handleGetAllBreakpoints(); + void handleRemoveBreakpoint(PFPSimDebugger::RemoveBreakpointMsg msg); + void handleWhoAmI(); + void handleNext(); + void handleGetPacketList(PFPSimDebugger::GetPacketListMsg msg); + void handleSetWatchpoint(PFPSimDebugger::SetWatchpointMsg msg); + void handleGetAllWatchpoints(); + void handleRemoveWatchpoint(PFPSimDebugger::RemoveWatchpointMsg msg); + void handleBacktrace(PFPSimDebugger::BacktraceMsg msg); + void handleEnableDisableBreakpoint( + PFPSimDebugger::EnableDisableBreakpointMsg msg); + void handleEnableDisableWatchpoint( + PFPSimDebugger::EnableDisableWatchpointMsg msg); + void handleIgnoreModule(PFPSimDebugger::IgnoreModuleMsg msg); + void handleGetAllIgnoreModules(); + void handleGetSimulationTime(); + void handleBreakOnPacketDrop(PFPSimDebugger::BreakOnPacketDropMsg msg); + void handleGetDroppedPackets(); + void handleCPCommand(PFPSimDebugger::CPCommandMsg msg); + void handleGetTableEntries(); + + /** + * Send pfpdb a generic reply so that it may regain control. + */ + void sendGenericReply(); + + /** + * Send pfpdb a message that indicates that the previous request failed. + */ + void sendRequestFailed(); +}; + +}; // namespace db +}; // namespace core +}; // namespace pfp + +#endif // CORE_DEBUGGER_DEBUGGERIPCSERVER_H_ diff --git a/pfpsim/core/debugger/DebuggerMessages.cpp b/pfpsim/core/debugger/DebuggerMessages.cpp new file mode 100644 index 0000000..de9f6e2 --- /dev/null +++ b/pfpsim/core/debugger/DebuggerMessages.cpp @@ -0,0 +1,311 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "DebuggerMessages.h" +#include +#include + +namespace pfp { +namespace core { +namespace db { + +DebuggerMessage::DebuggerMessage(PFPSimDebugger::DebugMsg_Type type) { + message.set_type(type); +} + +DebuggerMessage::~DebuggerMessage() { + message.release_message(); +} + +bool DebuggerMessage::SerializeToString(std::string* output) { + return message.SerializeToString(output); +} + +std::string DebuggerMessage::DebugString() { + return message.DebugString(); +} + +PFPSimDebugger::DebugMsg_Type DebuggerMessage::type() { + return message.type(); +} + +CounterValueMessage::CounterValueMessage(std::string name, int value) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_CounterValue) { + PFPSimDebugger::CounterValueMsg counter_value_msg; + counter_value_msg.set_name(name); + counter_value_msg.set_value(value); + message.set_message(counter_value_msg.SerializeAsString()); +} + +AllCounterValuesMessage::AllCounterValuesMessage(std::string *name_list, + int *value_list, int num) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_AllCounterValues) { + PFPSimDebugger::AllCounterValuesMsg all_counters_message; + for (int i = 0; i < num; i++) { + all_counters_message.add_name_list(name_list[i]); + all_counters_message.add_value_list(value_list[i]); + } + message.set_message(all_counters_message.SerializeAsString()); +} + +BreakpointHitMessage::BreakpointHitMessage(int id, std::string module, + int packet_id, double sim_time, bool read) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_BreakpointHit) { + PFPSimDebugger::BreakpointHitMsg breakpoint_hit_message; + breakpoint_hit_message.set_id(id); + breakpoint_hit_message.set_module(module); + breakpoint_hit_message.set_packet_id(packet_id); + breakpoint_hit_message.set_time_ns(sim_time); + if (read) { + breakpoint_hit_message.set_read("1"); + } else { + breakpoint_hit_message.set_read("0"); + } + message.set_message(breakpoint_hit_message.SerializeAsString()); +} + +GenericAcknowledgeMessage::GenericAcknowledgeMessage( + PFPSimDebugger::GenericAcknowledgeMsg_Status status) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_GenericAcknowledge) { + PFPSimDebugger::GenericAcknowledgeMsg generic_acknowledge; + generic_acknowledge.set_status(status); + message.set_message(generic_acknowledge.SerializeAsString()); +} + +AllBreakpointValuesMessage::AllBreakpointValuesMessage( + std::vector breakpoint_list) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_AllBreakpointValues) { + PFPSimDebugger::AllBreakpointValuesMsg all_bkpt_values; + for (auto bkpt = breakpoint_list.begin(); + bkpt != breakpoint_list.end(); bkpt++) { + all_bkpt_values.add_id_list(bkpt->getID()); + if (bkpt->temp) { + all_bkpt_values.add_temporary("1"); + } else { + all_bkpt_values.add_temporary("0"); + } + if (bkpt->disabled) { + all_bkpt_values.add_disabled("1"); + } else { + all_bkpt_values.add_disabled("0"); + } + PFPSimDebugger::AllBreakpointValuesMsg_BreakpointConditionList + *bkpt_conditions = all_bkpt_values.add_breakpoint_condition_list(); + for (auto it = bkpt->conditions.begin(); + it != bkpt->conditions.end(); it++) { + if (it->first == Breakpoint::BreakpointCondition::BREAK_ON_MODULE_READ) { + bkpt_conditions->add_condition_list( + PFPSimDebugger::BreakpointCondition::BREAK_ON_MODULE_READ); + } else if (it->first + == Breakpoint::BreakpointCondition::BREAK_ON_MODULE_WRITE) { + bkpt_conditions->add_condition_list( + PFPSimDebugger::BreakpointCondition::BREAK_ON_MODULE_WRITE); + } else if (it->first + == Breakpoint::BreakpointCondition::BREAK_ON_PACKET_ID) { + bkpt_conditions->add_condition_list( + PFPSimDebugger::BreakpointCondition::BREAK_ON_PACKET_ID); + } else if (it->first == Breakpoint::BreakpointCondition::BREAK_AT_TIME) { + bkpt_conditions->add_condition_list( + PFPSimDebugger::BreakpointCondition::BREAK_AT_TIME); + } + bkpt_conditions->add_value_list(it->second); + } + } + message.set_message(all_bkpt_values.SerializeAsString()); +} + +WhoAmIReplyMessage::WhoAmIReplyMessage(int packet_id) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_WhoAmIReply) { + PFPSimDebugger::WhoAmIReplyMsg whoami_reply; + whoami_reply.set_packet_id(packet_id); + message.set_message(whoami_reply.SerializeAsString()); +} + +PacketListValuesMessage::PacketListValuesMessage(int *id_list, + std::string *location_list, double *time_list, int num) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_PacketListValues) { + PFPSimDebugger::PacketListValuesMsg packet_list_values; + for (int i = 0; i < num; i++) { + packet_list_values.add_id_list(id_list[i]); + packet_list_values.add_location_list(location_list[i]); + packet_list_values.add_time_list(time_list[i]); + } + message.set_message(packet_list_values.SerializeAsString()); +} + +WatchpointHitMessage::WatchpointHitMessage(int id, std::string counter_name, + int old_value, int new_value) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_WatchpointHit) { + PFPSimDebugger::WatchpointHitMsg watchpoint_hit; + watchpoint_hit.set_id(id); + watchpoint_hit.set_counter_name(counter_name); + watchpoint_hit.set_old_value(old_value); + watchpoint_hit.set_new_value(new_value); + message.set_message(watchpoint_hit.SerializeAsString()); +} + +AllWatchpointValuesMessage::AllWatchpointValuesMessage( + std::vector watchpoint_list) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_AllWatchpointValues) { + PFPSimDebugger::AllWatchpointValuesMsg all_watchpoint_values; + int num = watchpoint_list.size(); + for (int i = 0; i < num; i++) { + all_watchpoint_values.add_id_list(watchpoint_list[i].getID()); + all_watchpoint_values.add_name_list(watchpoint_list[i].getCounterName()); + if (watchpoint_list[i].disabled) { + all_watchpoint_values.add_disabled("1"); + } else { + all_watchpoint_values.add_disabled("0"); + } + } + message.set_message(all_watchpoint_values.SerializeAsString()); +} + +BacktraceReplyMessage::BacktraceReplyMessage(int id, + std::string *module_list, double *read_time_list, + double *write_time_list, int num) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_BacktraceReply) { + PFPSimDebugger::BacktraceReplyMsg backtrace_reply; + backtrace_reply.set_packet_id(id); + for (int i = 0; i < num; i++) { + backtrace_reply.add_module_list(module_list[i]); + backtrace_reply.add_read_time_list(read_time_list[i]); + backtrace_reply.add_write_time_list(write_time_list[i]); + } + message.set_message(backtrace_reply.SerializeAsString()); +} + +SimulationEndMessage::SimulationEndMessage() + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_SimulationEnd) { + PFPSimDebugger::SimulationEndMsg sim_end; + message.set_message(sim_end.SerializeAsString()); +} + +SimulationStoppedMessage::SimulationStoppedMessage( + std::string module, int packet_id, double time_ns, bool read) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_SimulationStopped) { + PFPSimDebugger::SimulationStoppedMsg sim_stopped; + sim_stopped.set_module(module); + sim_stopped.set_packet_id(packet_id); + sim_stopped.set_time(time_ns); + sim_stopped.set_read(read); + message.set_message(sim_stopped.SerializeAsString()); +} + +AllIgnoreModulesMessage::AllIgnoreModulesMessage( + std::string *module_list, int num) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_AllIgnoreModules) { + PFPSimDebugger::AllIgnoreModulesMsg all_ignore_modules; + for (int i = 0; i < num; i++) { + all_ignore_modules.add_module_list(module_list[i]); + } + message.set_message(all_ignore_modules.SerializeAsString()); +} + +SimulationTimeMessage::SimulationTimeMessage(double sim_time) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_SimulationTime) { + PFPSimDebugger::SimulationTimeMsg sim_time_msg; + sim_time_msg.set_time_ns(sim_time); + message.set_message(sim_time_msg.SerializeAsString()); +} + +PacketDroppedMessage::PacketDroppedMessage(int id, + std::string module, std::string reason) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_PacketDropped) { + PFPSimDebugger::PacketDroppedMsg packet_dropped; + packet_dropped.set_packet_id(id); + packet_dropped.set_module(module); + packet_dropped.set_reason(reason); + message.set_message(packet_dropped.SerializeAsString()); +} + +DroppedPacketsMessage::DroppedPacketsMessage(int *id_list, + std::string *module_list, std::string *reason_list, int num) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_DroppedPackets) { + PFPSimDebugger::DroppedPacketsMsg dropped_packets; + for (int i = 0; i < num; i++) { + dropped_packets.add_packet_id_list(id_list[i]); + dropped_packets.add_module_list(module_list[i]); + dropped_packets.add_reason_list(reason_list[i]); + } + message.set_message(dropped_packets.SerializeAsString()); +} + +TableEntriesMessage::TableEntriesMessage( + std::vector table_entries) + : DebuggerMessage(PFPSimDebugger::DebugMsg_Type_TableEntries) { + PFPSimDebugger::TableEntriesMsg msg; + for (auto it = table_entries.begin(); it != table_entries.end(); it++) { + PFPSimDebugger::TableEntriesMsg_TableEntry *entry = msg.add_entry_list(); + entry->set_table_name(it->table_name); + entry->set_action_name(it->action_name); + entry->set_handle(it->handle); + PFPSimDebugger::TableEntriesMsg_TableEntryStatus status + = PFPSimDebugger::TableEntriesMsg_TableEntryStatus_NONE; + switch (it->status) { + case CPDebuggerInterface::TableEntryStatus::OK: + { + status = PFPSimDebugger::TableEntriesMsg_TableEntryStatus_OK; + break; + } + case CPDebuggerInterface::TableEntryStatus::INSERTING: + { + status = PFPSimDebugger::TableEntriesMsg_TableEntryStatus_INSERTING; + break; + } + case CPDebuggerInterface::TableEntryStatus::DELETING: + { + status = PFPSimDebugger::TableEntriesMsg_TableEntryStatus_DELETING; + break; + } + case CPDebuggerInterface::TableEntryStatus::MODIFYING: + { + status = PFPSimDebugger::TableEntriesMsg_TableEntryStatus_MODIFYING; + break; + } + default: + { + break; + } + } + entry->set_status(status); + entry->add_match_key_list(it->match_key); + for (auto data = it->action_data.begin(); + data != it->action_data.end(); data++) { + entry->add_action_data_list(*data); + } + } + + message.set_message(msg.SerializeAsString()); +} + +}; // namespace db +}; // namespace core +}; // namespace pfp diff --git a/pfpsim/core/debugger/DebuggerMessages.h b/pfpsim/core/debugger/DebuggerMessages.h new file mode 100644 index 0000000..51bb512 --- /dev/null +++ b/pfpsim/core/debugger/DebuggerMessages.h @@ -0,0 +1,242 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** +* @file DebuggerMessages.h +* Defines the message objects that can be sent as replies to pfpdb through the DebuggerIPCServer +* +* Created on: January 27, 2016 +* Author: Eric Tremblay +*/ + +#ifndef CORE_DEBUGGER_DEBUGGERMESSAGES_H_ +#define CORE_DEBUGGER_DEBUGGERMESSAGES_H_ + +#include +#include +#include + +#include "../proto/PFPSimDebugger.pb.h" +#include "Breakpoint.h" +#include "Watchpoint.h" +#include "CPDebuggerInterface.h" + +namespace pfp { +namespace core { +namespace db { + +/** + * Base class for an messages that will be sent to pfpdb via the DebuggerIPCServer. + */ +class DebuggerMessage { + public: + /** + * Default Constructor + * @param type The type of the message as indicate in the proto file. This allows pfpdb to figure out what message it is receiving. + */ + explicit DebuggerMessage(PFPSimDebugger::DebugMsg_Type type); + + /** + * Destructor + */ + ~DebuggerMessage(); + + /** + * Serializes the message to a string using protobuf so that it can easily be send via the IPC. + * @param output The serialized string. + * @return True if the serialization was successful, false otherwise. + */ + bool SerializeToString(std::string* output); + + /** + * Creates a human-readable string representation of the message so that it can be printed in a log. + * @return Human-readable string representation of the message. + */ + std::string DebugString(); + + /** + * Get the type of the message. + * @return Type of message. + */ + PFPSimDebugger::DebugMsg_Type type(); + + protected: + PFPSimDebugger::DebugMsg message; /*!< protobuf message object. */ +}; + +/** + * Message which returns the value of a counter. + */ +class CounterValueMessage: public DebuggerMessage { + public: + CounterValueMessage(std::string name, int value); +}; + +/** + * Message which returns the value of all the counters. + */ +class AllCounterValuesMessage: public DebuggerMessage { + public: + AllCounterValuesMessage(std::string *name_list, int *value_list, int num); +}; + +/** + * Message which indicates that a breakpoint has been hit. + */ +class BreakpointHitMessage: public DebuggerMessage { + public: + BreakpointHitMessage(int id, std::string module, int packet_id, + double sim_time, bool read); +}; + +/** + * Message which is sent to indicate that a request was successful or not when the debugger isn't expecting any information back. + */ +class GenericAcknowledgeMessage: public DebuggerMessage { + public: + GenericAcknowledgeMessage( + PFPSimDebugger::GenericAcknowledgeMsg_Status status); +}; + +/** + * Message which returns the list of all breakpoints. + */ +class AllBreakpointValuesMessage: public DebuggerMessage { + public: + explicit AllBreakpointValuesMessage(std::vector breakpoint_list); +}; + +/** + * Message which returns the ID of the packet which is current being followed. + */ +class WhoAmIReplyMessage: public DebuggerMessage { + public: + explicit WhoAmIReplyMessage(int packet_id); +}; + +/** + * Message which returns the list of all the packets currently within the simulation. + */ +class PacketListValuesMessage: public DebuggerMessage { + public: + PacketListValuesMessage(int *id_list, std::string *location_list, + double *time_list, int num); +}; + +/** + * Message which indicates that a watchpoint has been hit. + */ +class WatchpointHitMessage: public DebuggerMessage { + public: + WatchpointHitMessage(int id, std::string counter_name, + int old_value, int new_value); +}; + +/** + * Message which returns the list of all the watchpoints. + */ +class AllWatchpointValuesMessage: public DebuggerMessage { + public: + explicit AllWatchpointValuesMessage(std::vector watchpoint_list); +}; + +/** + * Message which returns the backtrace of a packet. + */ +class BacktraceReplyMessage: public DebuggerMessage { + public: + BacktraceReplyMessage(int id, std::string *module_list, + double *read_time_list, double *write_time_list, int num); +}; + +/** + * Message which indicates that the simulation has ended. + */ +class SimulationEndMessage: public DebuggerMessage { + public: + SimulationEndMessage(); +}; + +/** + * Message which indicates that the simulation has stopped. + */ +class SimulationStoppedMessage: public DebuggerMessage { + public: + SimulationStoppedMessage(std::string module, int packet_id, + double time_ns, bool read); +}; + +/** + * Message which returns the list of all the modules that are being ignored. + */ +class AllIgnoreModulesMessage: public DebuggerMessage { + public: + AllIgnoreModulesMessage(std::string *module_list, int num); +}; + +/** + * Message which returns the current simulation time. + */ +class SimulationTimeMessage: public DebuggerMessage { + public: + explicit SimulationTimeMessage(double sim_time); +}; + +/** + * Message which indicates that a packet has been dropped. + */ +class PacketDroppedMessage: public DebuggerMessage { + public: + PacketDroppedMessage(int id, std::string module, std::string reason); +}; + +/** + * Message which returns the list of all dropped packets. + */ +class DroppedPacketsMessage: public DebuggerMessage { + public: + DroppedPacketsMessage(int *id_list, std::string *module_list, + std::string *reason_list, int num); +}; + +/** + * Message which returns the list of all the table entries in all of the tables. + */ +class TableEntriesMessage: public DebuggerMessage { + public: + TableEntriesMessage( + std::vector table_entries); +}; + +}; // namespace db +}; // namespace core +}; // namespace pfp + +#endif // CORE_DEBUGGER_DEBUGGERMESSAGES_H_ diff --git a/pfpsim/core/debugger/DebuggerPacket.cpp b/pfpsim/core/debugger/DebuggerPacket.cpp new file mode 100644 index 0000000..d9fd97c --- /dev/null +++ b/pfpsim/core/debugger/DebuggerPacket.cpp @@ -0,0 +1,90 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "DebuggerPacket.h" +#include +#include + +namespace pfp { +namespace core { +namespace db { + +DebuggerPacket::DebuggerPacket() {} + +DebuggerPacket::DebuggerPacket(int id, std::string location, double time_ns) + : packet_id(id), current_location(location), last_notify_time(time_ns) {} + +void DebuggerPacket::updateTraceReadTime(std::string mod, double rtime) { + PacketLocation pl; + pl.module = mod; + pl.read_time = rtime; + trace.push_back(pl); +} + +void DebuggerPacket::updateTraceWriteTime(std::string mod, double wtime) { + for (auto pl = trace.rbegin(); pl != trace.rend(); pl++) { + if (pl->module == mod && pl->write_time == -1) { + pl->write_time = wtime; + return; + } + } + PacketLocation pl; + pl.module = mod; + pl.write_time = wtime; + trace.push_back(pl); +} + +int DebuggerPacket::getID() const { + return packet_id; +} + +std::string DebuggerPacket::getLocation() const { + return current_location; +} + +double DebuggerPacket::getTime() const { + return last_notify_time; +} + +std::vector DebuggerPacket::getTrace() const { + return trace; +} + +void DebuggerPacket::setCurrentLocation(std::string loc) { + current_location = loc; +} + +void DebuggerPacket::setTime(double t) { + last_notify_time = t; +} + +}; // namespace db +}; // namespace core +}; // namespace pfp diff --git a/pfpsim/core/debugger/DebuggerPacket.h b/pfpsim/core/debugger/DebuggerPacket.h new file mode 100644 index 0000000..db03d03 --- /dev/null +++ b/pfpsim/core/debugger/DebuggerPacket.h @@ -0,0 +1,140 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** +* @file DebuggerPacket.h +* Defines a class representation of a packet in the debugger context. +* +* Created on: February 8, 2016 +* Author: Eric Tremblay +*/ + +#ifndef CORE_DEBUGGER_DEBUGGERPACKET_H_ +#define CORE_DEBUGGER_DEBUGGERPACKET_H_ + +#include +#include + +namespace pfp { +namespace core { +namespace db { + +/** + * Class representation of a packet for pfpdb. + */ +class DebuggerPacket { + public: + /** + * Data structure to represent the location of a packet as well as the times at which it entered and left this location. + */ + class PacketLocation { + public: + PacketLocation(): module(""), read_time(-1), write_time(-1) {} + + std::string module; + double read_time; + double write_time; + }; + + /** + * Empty Constructor + */ + DebuggerPacket(); + + /** + * Constructor + * @param id ID of packet. + * @param location Current module the packet is in. + * @param time_ns Current simulation time in nanoseconds. + */ + DebuggerPacket(int id, std::string location, double time_ns); + + /** + * Update the time at which the packet entered a module. + * @param mod Module name. + * @param rtime Time at which the packet entered the module. + */ + void updateTraceReadTime(std::string mod, double rtime); + + /** + * Update the time at which the packet left a module. + * @param mod Module name. + * @param wtime Time at which the packet left the module. + */ + void updateTraceWriteTime(std::string mod, double wtime); + + /** + * Get the packet ID. + * @return Packet id. + */ + int getID() const; + + /** + * Get current packet location (which module the packet is in). + * @return Packet location. + */ + std::string getLocation() const; + + /** + * Get the time of the last update (read or write) to this packet. + * @return Time of last update. + */ + double getTime() const; + + /** + * Get backtrace of the packet. + * Includes which modules it when into as well as the times at which it entered and left the module. + */ + std::vector getTrace() const; + + /** + * Set the current location of the packet. + * @param loc Module the packet is currently in. + */ + void setCurrentLocation(std::string loc); + + /** + * Set the current time of the packet. Normally corresponds to the last update on the packet. + * @param t Current Time. + */ + void setTime(double t); + + private: + int packet_id; /*!< Packet ID. */ + std::string current_location; /*!< Current Location. */ + double last_notify_time; /*!< Time of last update. */ + std::vector trace; /*!< Backtrace of packet. */ +}; + +}; // namespace db +}; // namespace core +}; // namespace pfp + +#endif // CORE_DEBUGGER_DEBUGGERPACKET_H_ diff --git a/pfpsim/core/debugger/README.md b/pfpsim/core/debugger/README.md new file mode 100644 index 0000000..a382e2a --- /dev/null +++ b/pfpsim/core/debugger/README.md @@ -0,0 +1,26 @@ +Debugger Dependencies {#debug_depends} +================================= +Protocol Buffers v2.6.1: https://github.com/google/protobuf/releases/tag/v2.6.1 + - Installation: follow protobuf's README.md file with the following modifications + - use the following configure command: + ./configure CC=clang CXX=clang++ CXXFLAGS='-std=c++11 -stdlib=libstdc++ -O3 -g' LDFLAGS='-stdlib=libstdc++' LIBS="-lc++ -lc++abi" + make + sudo make install + sudo ldconfig # may or may not be necessary + if using python2.7: sudo pip2 install protobuf + if using python3: sudo pip3 install protobuf==3.0.0b2 + - 'make check' will fail and so don't bother running it. + +nanomsg v0.5-beta: http://download.nanomsg.org/nanomsg-0.5-beta.tar.gz + +nnpy (nanomsg python package): https://github.com/nanomsg/nnpy + - Installation: + - if using python2.7: sudo pip2 install nnpy + - if using python3: sudo pip3 install nnpy + + -- Note: nanomsg and nnpy are also used by P4. You may also use P4's install scripts in behavioral-model/build/travis + +Python tabulate library: https://pypi.python.org/pypi/tabulate + - Installation: + - if using python2.7: sudo pip2 install tabulate + - if using python3: sudo pip3 install tabulate diff --git a/pfpsim/core/debugger/Watchpoint.cpp b/pfpsim/core/debugger/Watchpoint.cpp new file mode 100644 index 0000000..76bb5eb --- /dev/null +++ b/pfpsim/core/debugger/Watchpoint.cpp @@ -0,0 +1,59 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include "Watchpoint.h" +#include + +namespace pfp { +namespace core { +namespace db { + +int Watchpoint::next_id = 0; + +Watchpoint::Watchpoint(): disabled(false) { + id = next_id++; +} + +Watchpoint::Watchpoint(std::string name, bool dis): Watchpoint() { + counter_name = name; + disabled = dis; +} + +int Watchpoint::getID() const { + return id; +} + +std::string Watchpoint::getCounterName() const { + return counter_name; +} + +}; // namespace db +}; // namespace core +}; // namespace pfp diff --git a/pfpsim/core/debugger/Watchpoint.h b/pfpsim/core/debugger/Watchpoint.h new file mode 100644 index 0000000..900e7a6 --- /dev/null +++ b/pfpsim/core/debugger/Watchpoint.h @@ -0,0 +1,91 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** +* @file Watchpoint.h +* Defines a class representation of a watchpoint for the debugger. +* +* Created on: February 11, 2016 +* Author: Eric Tremblay +*/ + +#ifndef CORE_DEBUGGER_WATCHPOINT_H_ +#define CORE_DEBUGGER_WATCHPOINT_H_ + +#include +#include + +namespace pfp { +namespace core { +namespace db { + +/** + * Class representation of watchpoints. Watchpoints can be set on counters. + * When set, the user will be notified if the value of a counter changes. + */ +class Watchpoint { + public: + /** + * Default Constructor. + */ + Watchpoint(); + + /** + * Constructor + * @param name Name of counter the Watchpoint is set on. + * @param dis = Indicates if the Watchpoint is disabled. + */ + explicit Watchpoint(std::string name, bool dis = false); + + /** + * Get the unique ID of the Watchpoint. + * @return ID of Watchpoint. + */ + int getID() const; + + /** + * Get name of counter the Watchpoint is set on. + * @return Name of counter. + */ + std::string getCounterName() const; + + bool disabled; /*!< Indicates if the Watchpoint is disabled. */ + + private: + int id; /*!< Unique ID of Watchpoint. */ + static int next_id; /*!< ID of next Watchpoint that will be created. */ + std::string counter_name; /*!< Name of counter the Watchpoint is set on. */ +}; + +}; // namespace db +}; // namespace core +}; // namespace pfp + +#endif // CORE_DEBUGGER_WATCHPOINT_H_ diff --git a/pfpsim/core/json.hpp b/pfpsim/core/json.hpp new file mode 100644 index 0000000..5590c0c --- /dev/null +++ b/pfpsim/core/json.hpp @@ -0,0 +1,8738 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 2.0.0 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +Copyright (c) 2013-2016 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 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 + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ + + +/*! +@brief unnamed namespace with internal helper functions +@since version 1.0.0 +*/ +namespace +{ +/*! +@brief Helper to determine whether there's a key_type for T. +@sa http://stackoverflow.com/a/7728728/266378 +*/ +template +struct has_mapped_type +{ + private: + template static char test(typename C::mapped_type*); + template static char (&test(...))[2]; + public: + static constexpr bool value = sizeof(test(0)) == 1; +}; + +} + +/*! +@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) + +@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. + +@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 +*/ +template < + 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 + > +class basic_json +{ + private: + /// workaround type for MSVC + using basic_json_t = basic_json; + + public: + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// @{ + + /// 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; + + // forward declaration + template class json_reverse_iterator; + + /// an iterator for a basic_json container + class iterator; + /// a const iterator for a basic_json container + class const_iterator; + /// 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(); + } + + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// @{ + + /*! + @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, later stored name/value + pairs overwrite previously stored name/value pairs, leaving the used + names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will + be treated as equal and both stored as `{"key": 1}`. + - 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 constraint explicitly. + 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, + AllocatorType>>; + + /*! + @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 constraint explicitly. + 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 + + #### 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; + + /// @} + + + /////////////////////////// + // 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 is_null(), @ref + is_object(), @ref is_array(), @ref is_string(), @ref is_boolean(), @ref + is_number(), and @ref is_discarded() rely on it. + + @since version 1.0.0 + */ + enum class value_t : 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 (integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + discarded ///< discarded by the the parser callback function + }; + + + private: + /// helper for exception-safe object creation + template + static T* create(Args&& ... args) + { + AllocatorType alloc; + auto deleter = [&](T * object) + { + alloc.deallocate(object, 1); + }; + std::unique_ptr object(alloc.allocate(1), deleter); + alloc.construct(object.get(), std::forward(args)...); + return object.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. + + @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; + } + + default: + { + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) + { + string = create(value); + } + + /// constructor for objects + json_value(const object_t& value) + { + object = create(value); + } + + /// constructor for arrays + json_value(const array_t& value) + { + array = create(value); + } + }; + + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /*! + @brief JSON callback events + + This enumeration lists the parser events that can trigger calling a + callback function of type @ref parser_callback_t during parsing. + + @since version 1.0.0 + */ + 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 + }; + + /*! + @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(std::istream&, parser_callback_t) or + @ref parse(const string_t&, parser_callback_t), 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 + + 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(std::istream&, parser_callback_t) or + @ref parse(const string_t&, parser_callback_t) for examples + + @since version 1.0.0 + */ + using parser_callback_t = std::function; + + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// @{ + + /*! + @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] value_type the type of the value to create + + @complexity Constant. + + @throw std::bad_alloc if allocation for object, array, or string value + fails + + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @sa @ref basic_json(std::nullptr_t) -- create a `null` value + @sa @ref basic_json(boolean_t value) -- create a boolean value + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const object_t&) -- create a object value + @sa @ref basic_json(const array_t&) -- create a array value + @sa @ref basic_json(const number_float_t) -- create a number + (floating-point) value + @sa @ref basic_json(const number_integer_t) -- create a number (integer) + value + @sa @ref basic_json(const number_unsigned_t) -- create a number (unsigned) + value + + @since version 1.0.0 + */ + basic_json(const value_t value_type) + : m_type(value_type), m_value(value_type) + {} + + /*! + @brief create a null object (implicitly) + + Create a `null` JSON value. This is the implicit version of the `null` + value constructor as it takes no parameters. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor 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. + - As postcondition, it holds: `basic_json().empty() == true`. + + @liveexample{The following code shows the constructor for a `null` JSON + value.,basic_json} + + @sa @ref basic_json(std::nullptr_t) -- create a `null` value + + @since version 1.0.0 + */ + basic_json() = default; + + /*! + @brief create a null object (explicitly) + + Create a `null` JSON value. This is the explicitly version of the `null` + value constructor as it takes a null pointer as parameter. It allows to + create `null` values by explicitly assigning a `nullptr` to a JSON value. + 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 null pointer + parameter.,basic_json__nullptr_t} + + @sa @ref basic_json() -- default constructor (implicitly creating a `null` + value) + + @since version 1.0.0 + */ + basic_json(std::nullptr_t) noexcept + : basic_json(value_t::null) + {} + + /*! + @brief create an object (explicit) + + Create an object JSON value with a given content. + + @param[in] val a value for the object + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for object value fails + + @liveexample{The following code shows the constructor with an @ref object_t + parameter.,basic_json__object_t} + + @sa @ref basic_json(const CompatibleObjectType&) -- create an object value + from a compatible STL container + + @since version 1.0.0 + */ + basic_json(const object_t& val) + : m_type(value_t::object), m_value(val) + {} + + /*! + @brief create an object (implicit) + + Create an object JSON value with a given content. This constructor allows + any type @a CompatibleObjectType that can be used to construct values of + type @ref object_t. + + @tparam CompatibleObjectType An object type whose `key_type` and + `value_type` is compatible to @ref object_t. Examples include `std::map`, + `std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with + a `key_type` of `std::string`, and a `value_type` from which a @ref + basic_json value can be constructed. + + @param[in] val a value for the object + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for object value fails + + @liveexample{The following code shows the constructor with several + compatible object type parameters.,basic_json__CompatibleObjectType} + + @sa @ref basic_json(const object_t&) -- create an object value + + @since version 1.0.0 + */ + template ::value and + std::is_constructible::value, int>::type + = 0> + basic_json(const CompatibleObjectType& val) + : m_type(value_t::object) + { + using std::begin; + using std::end; + m_value.object = create(begin(val), end(val)); + } + + /*! + @brief create an array (explicit) + + Create an array JSON value with a given content. + + @param[in] val a value for the array + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for array value fails + + @liveexample{The following code shows the constructor with an @ref array_t + parameter.,basic_json__array_t} + + @sa @ref basic_json(const CompatibleArrayType&) -- create an array value + from a compatible STL containers + + @since version 1.0.0 + */ + basic_json(const array_t& val) + : m_type(value_t::array), m_value(val) + {} + + /*! + @brief create an array (implicit) + + Create an array JSON value with a given content. This constructor allows + any type @a CompatibleArrayType that can be used to construct values of + type @ref array_t. + + @tparam CompatibleArrayType An object type whose `value_type` is compatible + to @ref array_t. Examples include `std::vector`, `std::deque`, `std::list`, + `std::forward_list`, `std::array`, `std::set`, `std::unordered_set`, + `std::multiset`, and `unordered_multiset` with a `value_type` from which a + @ref basic_json value can be constructed. + + @param[in] val a value for the array + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for array value fails + + @liveexample{The following code shows the constructor with several + compatible array type parameters.,basic_json__CompatibleArrayType} + + @sa @ref basic_json(const array_t&) -- create an array value + + @since version 1.0.0 + */ + 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 and + not std::is_same::value and + std::is_constructible::value, int>::type + = 0> + basic_json(const CompatibleArrayType& val) + : m_type(value_t::array) + { + using std::begin; + using std::end; + m_value.array = create(begin(val), end(val)); + } + + /*! + @brief create a string (explicit) + + Create an string JSON value with a given content. + + @param[in] val a value for the string + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for string value fails + + @liveexample{The following code shows the constructor with an @ref string_t + parameter.,basic_json__string_t} + + @sa @ref basic_json(const typename string_t::value_type*) -- create a + string value from a character pointer + @sa @ref basic_json(const CompatibleStringType&) -- create a string value + from a compatible string container + + @since version 1.0.0 + */ + basic_json(const string_t& val) + : m_type(value_t::string), m_value(val) + {} + + /*! + @brief create a string (explicit) + + Create a string JSON value with a given content. + + @param[in] val a literal value for the string + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for string value fails + + @liveexample{The following code shows the constructor with string literal + parameter.,basic_json__string_t_value_type} + + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const CompatibleStringType&) -- create a string value + from a compatible string container + + @since version 1.0.0 + */ + basic_json(const typename string_t::value_type* val) + : basic_json(string_t(val)) + {} + + /*! + @brief create a string (implicit) + + Create a string JSON value with a given content. + + @param[in] val a value for the string + + @tparam CompatibleStringType an string type which is compatible to @ref + string_t, for instance `std::string`. + + @complexity Linear in the size of the passed @a val. + + @throw std::bad_alloc if allocation for string value fails + + @liveexample{The following code shows the construction of a string value + from a compatible type.,basic_json__CompatibleStringType} + + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const typename string_t::value_type*) -- create a + string value from a character pointer + + @since version 1.0.0 + */ + template ::value, int>::type + = 0> + basic_json(const CompatibleStringType& val) + : basic_json(string_t(val)) + {} + + /*! + @brief create a boolean (explicit) + + Creates a JSON boolean type from a given value. + + @param[in] val a boolean value to store + + @complexity Constant. + + @liveexample{The example below demonstrates boolean + values.,basic_json__boolean_t} + + @since version 1.0.0 + */ + basic_json(boolean_t val) noexcept + : m_type(value_t::boolean), m_value(val) + {} + + /*! + @brief create an integer number (explicit) + + Create an integer number JSON value with a given content. + + @tparam T A helper type to remove this function via SFINAE in case @ref + number_integer_t is the same as `int`. In this case, this constructor would + have the same signature as @ref basic_json(const int value). Note the + helper type @a T is not visible in this constructor's interface. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @liveexample{The example below shows the construction of an integer + number value.,basic_json__number_integer_t} + + @sa @ref basic_json(const int) -- create a number value (integer) + @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number + value (integer) from a compatible number type + + @since version 1.0.0 + */ + template::value) + and std::is_same::value + , int>::type + = 0> + basic_json(const number_integer_t val) noexcept + : m_type(value_t::number_integer), m_value(val) + {} + + /*! + @brief create an integer number from an enum type (explicit) + + Create an integer number JSON value with a given content. + + @param[in] val an integer to create a JSON number from + + @note This constructor allows to pass enums directly to a constructor. As + C++ has no way of specifying the type of an anonymous enum explicitly, we + can only rely on the fact that such values implicitly convert to int. As + int may already be the same type of number_integer_t, we may need to switch + off the constructor @ref basic_json(const number_integer_t). + + @complexity Constant. + + @liveexample{The example below shows the construction of an integer + number value from an anonymous enum.,basic_json__const_int} + + @sa @ref basic_json(const number_integer_t) -- create a number value + (integer) + @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number + value (integer) from a compatible number type + + @since version 1.0.0 + */ + basic_json(const int val) noexcept + : m_type(value_t::number_integer), + m_value(static_cast(val)) + {} + + /*! + @brief create an integer number (implicit) + + Create an integer number JSON value with a given content. This constructor + allows any type @a CompatibleNumberIntegerType that can be used to + construct values of type @ref number_integer_t. + + @tparam CompatibleNumberIntegerType An integer type which is compatible to + @ref number_integer_t. Examples include the types `int`, `int32_t`, `long`, + and `short`. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @liveexample{The example below shows the construction of several integer + number values from compatible + types.,basic_json__CompatibleIntegerNumberType} + + @sa @ref basic_json(const number_integer_t) -- create a number value + (integer) + @sa @ref basic_json(const int) -- create a number value (integer) + + @since version 1.0.0 + */ + template::value and + std::numeric_limits::is_integer and + std::numeric_limits::is_signed, + CompatibleNumberIntegerType>::type + = 0> + basic_json(const CompatibleNumberIntegerType val) noexcept + : m_type(value_t::number_integer), + m_value(static_cast(val)) + {} + + /*! + @brief create an unsigned integer number (explicit) + + Create an unsigned integer number JSON value with a given content. + + @tparam T helper type to compare number_unsigned_t and unsigned int + (not visible in) the interface. + + @param[in] val an integer to create a JSON number from + + @complexity Constant. + + @sa @ref basic_json(const CompatibleNumberUnsignedType) -- create a number + value (unsigned integer) from a compatible number type + + @since version 2.0.0 + */ + template::value) + and std::is_same::value + , int>::type + = 0> + basic_json(const number_unsigned_t val) noexcept + : m_type(value_t::number_unsigned), m_value(val) + {} + + /*! + @brief create an unsigned number (implicit) + + Create an unsigned number JSON value with a given content. This constructor + allows any type @a CompatibleNumberUnsignedType that can be used to + construct values of type @ref number_unsigned_t. + + @tparam CompatibleNumberUnsignedType An integer type which is compatible to + @ref number_unsigned_t. Examples may include the types `unsigned int`, + `uint32_t`, or `unsigned short`. + + @param[in] val an unsigned integer to create a JSON number from + + @complexity Constant. + + @sa @ref basic_json(const number_unsigned_t) -- create a number value + (unsigned) + + @since version 2.0.0 + */ + template < typename CompatibleNumberUnsignedType, typename + std::enable_if < + std::is_constructible::value and + std::numeric_limits::is_integer and + !std::numeric_limits::is_signed, + CompatibleNumberUnsignedType >::type + = 0 > + basic_json(const CompatibleNumberUnsignedType val) noexcept + : m_type(value_t::number_unsigned), + m_value(static_cast(val)) + {} + + /*! + @brief create a floating-point number (explicit) + + Create a floating-point number JSON value with a given content. + + @param[in] val a floating-point value to create a JSON number from + + @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 + disallows NaN values: + > Numeric values that cannot be represented in the grammar below (such + > as Infinity and NaN) are not permitted. + In case the parameter @a val is not a number, a JSON null value is + created instead. + + @complexity Constant. + + @liveexample{The following example creates several floating-point + values.,basic_json__number_float_t} + + @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number + value (floating-point) from a compatible number type + + @since version 1.0.0 + */ + basic_json(const number_float_t val) noexcept + : m_type(value_t::number_float), m_value(val) + { + // replace infinity and NAN by null + if (not std::isfinite(val)) + { + m_type = value_t::null; + m_value = json_value(); + } + } + + /*! + @brief create an floating-point number (implicit) + + Create an floating-point number JSON value with a given content. This + constructor allows any type @a CompatibleNumberFloatType that can be used + to construct values of type @ref number_float_t. + + @tparam CompatibleNumberFloatType A floating-point type which is compatible + to @ref number_float_t. Examples may include the types `float` or `double`. + + @param[in] val a floating-point to create a JSON number from + + @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 + disallows NaN values: + > Numeric values that cannot be represented in the grammar below (such + > as Infinity and NaN) are not permitted. + In case the parameter @a val is not a number, a JSON null value is + created instead. + + @complexity Constant. + + @liveexample{The example below shows the construction of several + floating-point number values from compatible + types.,basic_json__CompatibleNumberFloatType} + + @sa @ref basic_json(const number_float_t) -- create a number value + (floating-point) + + @since version 1.0.0 + */ + template::value and + std::is_floating_point::value>::type + > + basic_json(const CompatibleNumberFloatType val) noexcept + : basic_json(number_float_t(val)) + {} + + /*! + @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 now 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(std::initializer_list) + with an empty initializer list in this case + - arrays whose elements satisfy rule 2: use @ref + array(std::initializer_list) 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(std::initializer_list) and + @ref object(std::initializer_list). + + @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 std::domain_error 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; example: `"cannot create object from + initializer list"` + + @complexity Linear in the size of the initializer list @a init. + + @liveexample{The example below shows how JSON values are created from + initializer lists.,basic_json__list_init_t} + + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + basic_json(std::initializer_list init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // the initializer list could describe an object + bool is_an_object = true; + + // check if each element is an array with two elements whose first + // element is a string + for (const auto& element : init) + { + if (not element.is_array() or element.size() != 2 + or not element[0].is_string()) + { + // we found an element that makes it impossible to use the + // initializer list as object + is_an_object = false; + break; + } + } + + // 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 (manual_type == value_t::object and not is_an_object) + { + throw std::domain_error("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; + + assert(m_value.object != nullptr); + + for (auto& element : init) + { + m_value.object->emplace(*(element[0].m_value.string), element[1]); + } + } + else + { + // the initializer list describes an array -> create array + m_type = value_t::array; + m_value.array = create(init); + } + } + + /*! + @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(std::initializer_list, 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. + + @liveexample{The following code shows an example for the `array` + function.,array} + + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + static basic_json array(std::initializer_list init = + std::initializer_list()) + { + 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(std::initializer_list), 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(std::initializer_list, bool, value_t). + + @param[in] init initializer list to create an object from (optional) + + @return JSON object value + + @throw std::domain_error if @a init is not a pair whose first elements are + strings; thrown by + @ref basic_json(std::initializer_list, bool, value_t) + + @complexity Linear in the size of @a init. + + @liveexample{The following code shows an example for the `object` + function.,object} + + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + + @since version 1.0.0 + */ + static basic_json object(std::initializer_list init = + std::initializer_list()) + { + 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. As postcondition, + `std::distance(begin(),end()) == cnt` holds. + + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy + + @complexity Linear in @a cnt. + + @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); + } + + /*! + @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 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, std::out_of_range is thrown. + - In case of structured types (array, object), the constructor behaves + as similar versions for `std::vector`. + - In case of a null type, std::domain_error is thrown. + + @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) + + @throw std::domain_error if iterators are not compatible; that is, do not + belong to the same JSON value; example: `"iterators are not compatible"` + @throw std::out_of_range if iterators are for a primitive type (number, + boolean, or string) where an out of range error can be detected easily; + example: `"iterators out of range"` + @throw std::bad_alloc if allocation for object, array, or string fails + @throw std::domain_error if called with a null value; example: `"cannot use + construct with iterators from null"` + + @complexity Linear in distance between @a first and @a last. + + @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) : m_type(first.m_object->m_type) + { + // make sure iterator fits the current value + if (first.m_object != last.m_object) + { + throw std::domain_error("iterators are not compatible"); + } + + // 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 (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) + { + throw std::out_of_range("iterators out of range"); + } + break; + } + + default: + { + break; + } + } + + switch (m_type) + { + case value_t::number_integer: + { + assert(first.m_object != nullptr); + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + assert(first.m_object != nullptr); + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + assert(first.m_object != nullptr); + m_value.number_float = first.m_object->m_value.number_float; + break; + } + + case value_t::boolean: + { + assert(first.m_object != nullptr); + m_value.boolean = first.m_object->m_value.boolean; + break; + } + + case value_t::string: + { + assert(first.m_object != nullptr); + 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: + { + assert(first.m_object != nullptr); + throw std::domain_error("cannot use construct with iterators from " + first.m_object->type_name()); + } + } + } + + /*! + @brief construct a JSON value given an input stream + + @param[in,out] i stream to read a serialized JSON value 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) + + @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 constructing a JSON value from + a `std::stringstream` with and without callback + function.,basic_json__istream} + + @since version 2.0.0 + */ + explicit basic_json(std::istream& i, parser_callback_t cb = nullptr) + { + *this = parser(i, cb).parse(); + } + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + /*! + @brief copy constructor + + Creates a copy of a given JSON value. + + @param[in] other the JSON value to copy + + @complexity Linear in the size of @a other. + + @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)`. + + @throw std::bad_alloc if allocation for object, array, or string fails. + + @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) + { + switch (m_type) + { + case value_t::object: + { + assert(other.m_value.object != nullptr); + m_value = *other.m_value.object; + break; + } + + case value_t::array: + { + assert(other.m_value.array != nullptr); + m_value = *other.m_value.array; + break; + } + + case value_t::string: + { + assert(other.m_value.string != nullptr); + 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; + } + } + } + + /*! + @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 @a other is a JSON null value + + @complexity Constant. + + @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)) + { + // invalidate payload + other.m_type = value_t::null; + other.m_value = {}; + } + + /*! + @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 + ) + { + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + 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() + { + switch (m_type) + { + case value_t::object: + { + AllocatorType alloc; + alloc.destroy(m_value.object); + alloc.deallocate(m_value.object, 1); + break; + } + + case value_t::array: + { + AllocatorType alloc; + alloc.destroy(m_value.array); + alloc.deallocate(m_value.array, 1); + break; + } + + case value_t::string: + { + AllocatorType alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + break; + } + + default: + { + // all other types need no specific destructor + break; + } + } + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// @{ + + /*! + @brief serialization + + Serialization function for JSON values. The function tries to mimic + Python's @p json.dumps() function, and currently supports its @p indent + parameter. + + @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 + + @return string containing the serialization of the JSON value + + @complexity Linear. + + @liveexample{The following example shows the effect of different @a indent + parameters to the result of the serialization.,dump} + + @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0 + */ + string_t dump(const int indent = -1) const + { + std::stringstream ss; + + if (indent >= 0) + { + dump(ss, true, static_cast(indent)); + } + else + { + dump(ss, false, 0); + } + + return ss.str(); + } + + /*! + @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 + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `type()` for all JSON + types.,type} + + @since version 1.0.0 + */ + constexpr value_t type() const noexcept + { + return m_type; + } + + /*! + @brief return whether type is primitive + + This function returns true iff 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 iff 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 iff 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 iff 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 iff the JSON value is a number. This includes + both integer 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 iff the JSON value is an integer 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 iff 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 iff the JSON value is a floating-point number. + This excludes integer 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 iff 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 iff 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 iff 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 iff 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} + + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept + { + return m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get an object (explicit) + template ::value and + std::is_convertible::value + , int>::type = 0> + T get_impl(T*) const + { + if (is_object()) + { + assert(m_value.object != nullptr); + return T(m_value.object->begin(), m_value.object->end()); + } + else + { + throw std::domain_error("type must be object, but is " + type_name()); + } + } + + /// get an object (explicit) + object_t get_impl(object_t*) const + { + if (is_object()) + { + assert(m_value.object != nullptr); + return *(m_value.object); + } + else + { + throw std::domain_error("type must be object, but is " + type_name()); + } + } + + /// get an array (explicit) + template ::value and + not std::is_same::value and + not std::is_arithmetic::value and + not std::is_convertible::value and + not has_mapped_type::value + , int>::type = 0> + T get_impl(T*) const + { + if (is_array()) + { + T to_vector; + assert(m_value.array != nullptr); + std::transform(m_value.array->begin(), m_value.array->end(), + std::inserter(to_vector, to_vector.end()), [](basic_json i) + { + return i.get(); + }); + return to_vector; + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get an array (explicit) + template ::value and + not std::is_same::value + , int>::type = 0> + std::vector get_impl(std::vector*) const + { + if (is_array()) + { + std::vector to_vector; + assert(m_value.array != nullptr); + to_vector.reserve(m_value.array->size()); + std::transform(m_value.array->begin(), m_value.array->end(), + std::inserter(to_vector, to_vector.end()), [](basic_json i) + { + return i.get(); + }); + return to_vector; + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get an array (explicit) + template ::value and + not has_mapped_type::value + , int>::type = 0> + T get_impl(T*) const + { + if (is_array()) + { + assert(m_value.array != nullptr); + return T(m_value.array->begin(), m_value.array->end()); + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get an array (explicit) + array_t get_impl(array_t*) const + { + if (is_array()) + { + assert(m_value.array != nullptr); + return *(m_value.array); + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); + } + } + + /// get a string (explicit) + template ::value + , int>::type = 0> + T get_impl(T*) const + { + if (is_string()) + { + assert(m_value.string != nullptr); + return *m_value.string; + } + else + { + throw std::domain_error("type must be string, but is " + type_name()); + } + } + + /// get a number (explicit) + template::value + , int>::type = 0> + T get_impl(T*) const + { + switch (m_type) + { + case value_t::number_integer: + { + return static_cast(m_value.number_integer); + } + + case value_t::number_unsigned: + { + return static_cast(m_value.number_unsigned); + } + + case value_t::number_float: + { + return static_cast(m_value.number_float); + } + + default: + { + throw std::domain_error("type must be number, but is " + type_name()); + } + } + } + + /// get a boolean (explicit) + constexpr boolean_t get_impl(boolean_t*) const + { + return is_boolean() + ? m_value.boolean + : throw std::domain_error("type must be boolean, but is " + type_name()); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t*) 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*) const noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t*) 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*) const noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t*) 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*) const noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t*) 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*) 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*) 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*) 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*) 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*) 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*) 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*) const noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This funcion 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 std::domain_error 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<>() + using PointerType = typename std::add_pointer::type; + auto ptr = obj.template get_ptr(); + + if (ptr != nullptr) + { + return *ptr; + } + else + { + throw std::domain_error("incompatible ReferenceType for get_ref, actual type is " + + obj.type_name()); + } + } + + public: + + /// @name value access + /// @{ + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value. + + @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 + + @return copy of the JSON value, converted to type @a ValueType + + @throw std::domain_error in case passed type @a ValueType is incompatible + to JSON; example: `"type must be object, but is null"` + + @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`.,get__ValueType_const} + + @internal + The idea of using a casted null pointer to choose the correct + implementation is from . + @endinternal + + @sa @ref operator ValueType() const for implicit conversion + @sa @ref get() for pointer-member access + + @since version 1.0.0 + */ + template::value + , int>::type = 0> + ValueType get() const + { + return get_impl(static_cast(nullptr)); + } + + /*! + @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. + + @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 + { + // 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 + { + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a reference value (implicit) + + Implict 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. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + std::domain_error otherwise + + @throw std::domain_error in case passed type @a ReferenceType is + incompatible with the stored JSON value + + @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 std::domain_error in case passed type @a ValueType is incompatible + to JSON, thrown by @ref get() const + + @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 +#ifndef _MSC_VER // Fix for issue #167 operator<< abiguity under VS2015 + 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 + /// @{ + + /*! + @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 std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()`; example: `"array index 7 is out of range"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read and + written using `at()`.,at__size_type} + + @since version 1.0.0 + */ + reference at(size_type idx) + { + // at only works for arrays + if (is_array()) + { + try + { + assert(m_value.array != nullptr); + return m_value.array->at(idx); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + } + else + { + throw std::domain_error("cannot use at() with " + 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 std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()`; example: `"array index 7 is out of range"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + `at()`.,at__size_type_const} + + @since version 1.0.0 + */ + const_reference at(size_type idx) const + { + // at only works for arrays + if (is_array()) + { + try + { + assert(m_value.array != nullptr); + return m_value.array->at(idx); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + } + else + { + throw std::domain_error("cannot use at() with " + 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 std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()`; example: `"key "the fast" not found"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using `at()`.,at__object_t_key_type} + + @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 + */ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (is_object()) + { + try + { + assert(m_value.object != nullptr); + return m_value.object->at(key); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("key '" + key + "' not found"); + } + } + else + { + throw std::domain_error("cannot use at() with " + 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 std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()`; example: `"key "the fast" not found"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + `at()`.,at__object_t_key_type_const} + + @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 + */ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (is_object()) + { + try + { + assert(m_value.object != nullptr); + return m_value.object->at(key); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("key '" + key + "' not found"); + } + } + else + { + throw std::domain_error("cannot use at() with " + 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 std::domain_error if JSON is not an array or null; example: `"cannot + use operator[] with string"` + + @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(); + } + + // operator[] only works for arrays + if (is_array()) + { + // fill up array with null values until given idx is reached + assert(m_value.array != nullptr); + for (size_t i = m_value.array->size(); i <= idx; ++i) + { + m_value.array->push_back(basic_json()); + } + + return m_value.array->operator[](idx); + } + else + { + throw std::domain_error("cannot use operator[] with " + 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 std::domain_error if JSON is not an array; example: `"cannot use + operator[] with null"` + + @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 (is_array()) + { + assert(m_value.array != nullptr); + return m_value.array->operator[](idx); + } + else + { + throw std::domain_error("cannot use operator[] with " + 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 std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @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(); + } + + // operator[] only works for objects + if (is_object()) + { + assert(m_value.object != nullptr); + return m_value.object->operator[](key); + } + else + { + throw std::domain_error("cannot use operator[] with " + 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 + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @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 (is_object()) + { + assert(m_value.object != nullptr); + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + else + { + throw std::domain_error("cannot use operator[] with " + 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 std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @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 + */ + template + reference operator[](T * (&key)[n]) + { + return operator[](static_cast(key)); + } + + /*! + @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. + + @note This function is required for compatibility reasons with Clang. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @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 + */ + template + const_reference operator[](T * (&key)[n]) const + { + return operator[](static_cast(key)); + } + + /*! + @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 std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @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; + } + + // at only works for objects + if (is_object()) + { + assert(m_value.object != nullptr); + return m_value.object->operator[](key); + } + else + { + throw std::domain_error("cannot use operator[] with " + 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 + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @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 (is_object()) + { + assert(m_value.object != nullptr); + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + else + { + throw std::domain_error("cannot use operator[] with " + 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(std::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 std::domain_error if JSON is not an object; example: `"cannot use + value() with null"` + + @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, ValueType default_value) const + { + // at only works for objects + if (is_object()) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + else + { + return default_value; + } + } + else + { + throw std::domain_error("cannot use value() with " + type_name()); + } + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value() + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, 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 cast 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 std::out_of_range 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 cast 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 std::out_of_range when called on `null` value. + + @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 InteratorType 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 std::domain_error if called on a `null` value; example: `"cannot use + erase() with null"` + @throw std::domain_error if called on an iterator which does not belong to + the current JSON value; example: `"iterator does not fit current value"` + @throw std::out_of_range 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 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(InteratorType, InteratorType) -- 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> + InteratorType erase(InteratorType pos) + { + // make sure iterator fits the current value + if (this != pos.m_object) + { + throw std::domain_error("iterator does not fit current value"); + } + + InteratorType 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 (not pos.m_it.primitive_iterator.is_begin()) + { + throw std::out_of_range("iterator out of range"); + } + + if (is_string()) + { + delete m_value.string; + m_value.string = nullptr; + } + + m_type = value_t::null; + break; + } + + case value_t::object: + { + assert(m_value.object != nullptr); + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + assert(m_value.array != nullptr); + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + { + throw std::domain_error("cannot use erase() with " + 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 InteratorType 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 std::domain_error if called on a `null` value; example: `"cannot use + erase() with null"` + @throw std::domain_error if called on iterators which does not belong to + the current JSON value; example: `"iterators do not fit current value"` + @throw std::out_of_range 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(InteratorType) -- 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> + InteratorType erase(InteratorType first, InteratorType last) + { + // make sure iterator fits the current value + if (this != first.m_object or this != last.m_object) + { + throw std::domain_error("iterators do not fit current value"); + } + + InteratorType 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 (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) + { + throw std::out_of_range("iterators out of range"); + } + + if (is_string()) + { + delete m_value.string; + m_value.string = nullptr; + } + + m_type = value_t::null; + break; + } + + case value_t::object: + { + assert(m_value.object != nullptr); + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + assert(m_value.array != nullptr); + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + { + throw std::domain_error("cannot use erase() with " + 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 std::domain_error 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(InteratorType) -- removes the element at a given position + @sa @ref erase(InteratorType, InteratorType) -- 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 (is_object()) + { + assert(m_value.object != nullptr); + return m_value.object->erase(key); + } + else + { + throw std::domain_error("cannot use erase() with " + 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 std::domain_error when called on a type other than JSON array; + example: `"cannot use erase() with null"` + @throw std::out_of_range when `idx >= size()`; example: `"index 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(InteratorType) -- removes the element at a given position + @sa @ref erase(InteratorType, InteratorType) -- 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 (is_array()) + { + if (idx >= size()) + { + throw std::out_of_range("index out of range"); + } + + assert(m_value.array != nullptr); + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else + { + throw std::domain_error("cannot use erase() with " + 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. + + @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, past-the-end (see 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 + */ + iterator find(typename object_t::key_type key) + { + auto result = end(); + + if (is_object()) + { + assert(m_value.object != nullptr); + result.m_it.object_iterator = m_value.object->find(key); + } + + return result; + } + + /*! + @brief find an element in a JSON object + @copydoc find(typename object_t::key_type) + */ + const_iterator find(typename object_t::key_type key) const + { + auto result = cend(); + + if (is_object()) + { + assert(m_value.object != nullptr); + result.m_it.object_iterator = m_value.object->find(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). + + @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 + */ + size_type count(typename object_t::key_type key) const + { + // return 0 for all nonobject types + assert(not is_object() or m_value.object != nullptr); + return is_object() ? m_value.object->count(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()); + } + + private: + // forward declaration + template class iteration_proxy; + + 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. + + @note The name of this function is not yet final and may change in the + future. + */ + static iteration_proxy iterator_wrapper(reference cont) + { + return iteration_proxy(cont); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + static iteration_proxy iterator_wrapper(const_reference cont) + { + return iteration_proxy(cont); + } + + /// @} + + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /*! + @brief checks whether the container is empty + + Checks if a JSON value has no elements. + + @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()` + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy the + Container concept; that is, their `empty()` functions have constant + complexity. + + @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()`. + + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} + + @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: + { + assert(m_value.array != nullptr); + return m_value.array->empty(); + } + + case value_t::object: + { + assert(m_value.object != nullptr); + 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() + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy the + Container concept; that is, their size() functions have constant complexity. + + @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())`. + + @liveexample{The following code calls `size()` on the different value + types.,size} + + @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: + { + assert(m_value.array != nullptr); + return m_value.array->size(); + } + + case value_t::object: + { + assert(m_value.object != nullptr); + 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()` + + @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. + + @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. + + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + + @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: + { + assert(m_value.array != nullptr); + return m_value.array->max_size(); + } + + case value_t::object: + { + assert(m_value.object != nullptr); + 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: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @note Floating-point numbers are set to `0.0` which will be serialized to + `0`. The vale type remains @ref number_float_t. + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows the effect of `clear()` to different + JSON types.,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: + { + assert(m_value.string != nullptr); + m_value.string->clear(); + break; + } + + case value_t::array: + { + assert(m_value.array != nullptr); + m_value.array->clear(); + break; + } + + case value_t::object: + { + assert(m_value.object != nullptr); + 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 std::domain_error 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 (not(is_null() or is_array())) + { + throw std::domain_error("cannot use push_back() with " + type_name()); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + } + + // add element to array (move semantics) + assert(m_value.array != nullptr); + 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 (not(is_null() or is_array())) + { + throw std::domain_error("cannot use push_back() with " + type_name()); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + } + + // add element to array + assert(m_value.array != nullptr); + 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 std::domain_error 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 (not(is_null() or is_object())) + { + throw std::domain_error("cannot use push_back() with " + type_name()); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + } + + // add element to array + assert(m_value.object != nullptr); + 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 operator[](val.first); + } + + /*! + @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 std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + + @complexity Constant plus linear in the distance between 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 (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + assert(m_value.array != nullptr); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } + else + { + throw std::domain_error("cannot use insert() with " + 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 std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error 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 (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + assert(m_value.array != nullptr); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } + else + { + throw std::domain_error("cannot use insert() with " + 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 std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + @throw std::domain_error if @a first and @a last do not belong to the same + JSON value; example: `"iterators do not fit"` + @throw std::domain_error 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 (not is_array()) + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + if (first.m_object != last.m_object) + { + throw std::domain_error("iterators do not fit"); + } + + if (first.m_object == this or last.m_object == this) + { + throw std::domain_error("passed iterators may not belong to container"); + } + + // insert to array and return iterator + iterator result(this); + assert(m_value.array != nullptr); + 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 std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error 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, std::initializer_list ilist) + { + // insert only works for arrays + if (not is_array()) + { + throw std::domain_error("cannot use insert() with " + type_name()); + } + + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + assert(m_value.array != nullptr); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist); + return result; + } + + /*! + @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); + } + + /*! + @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 std::domain_error 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 (is_array()) + { + assert(m_value.array != nullptr); + std::swap(*(m_value.array), other); + } + else + { + throw std::domain_error("cannot use swap() with " + 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 std::domain_error 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 (is_object()) + { + assert(m_value.object != nullptr); + std::swap(*(m_value.object), other); + } + else + { + throw std::domain_error("cannot use swap() with " + 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 std::domain_error 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 (is_string()) + { + assert(m_value.string != nullptr); + std::swap(*(m_value.string), other); + } + else + { + throw std::domain_error("cannot use swap() with " + type_name()); + } + } + + /// @} + + + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + private: + /*! + @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 + + @since version 1.0.0 + */ + friend 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 + } + }; + + // discarded values are not comparable + if (lhs == value_t::discarded or rhs == value_t::discarded) + { + return false; + } + + return order[static_cast(lhs)] < order[static_cast(rhs)]; + } + + public: + /*! + @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. + - Integer and floating-point numbers are automatically converted before + comparison. Floating-point numbers are compared indirectly: two + floating-point numbers `f1` and `f2` are considered equal if neither + `f1 > f2` nor `f2 > f1` holds. + - Two JSON null values are equal. + + @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 + + @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: + { + assert(lhs.m_value.array != nullptr); + assert(rhs.m_value.array != nullptr); + return *lhs.m_value.array == *rhs.m_value.array; + } + case value_t::object: + { + assert(lhs.m_value.object != nullptr); + assert(rhs.m_value.object != nullptr); + return *lhs.m_value.object == *rhs.m_value.object; + } + case value_t::null: + { + return true; + } + case value_t::string: + { + assert(lhs.m_value.string != nullptr); + assert(rhs.m_value.string != nullptr); + 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 + + The functions compares the given JSON value against a null pointer. As the + null pointer can be used to initialize a JSON value to null, a comparison + of JSON value @a v with a null pointer should be equivalent to call + `v.is_null()`. + + @param[in] v JSON value to consider + @return whether @a v is null + + @complexity Constant. + + @liveexample{The example compares several JSON types to the null pointer. + ,operator__equal__nullptr_t} + + @since version 1.0.0 + */ + friend bool operator==(const_reference v, std::nullptr_t) noexcept + { + return v.is_null(); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, std::nullptr_t) + */ + friend bool operator==(std::nullptr_t, const_reference v) noexcept + { + return v.is_null(); + } + + /*! + @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. + + @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 + + The functions compares the given JSON value against a null pointer. As the + null pointer can be used to initialize a JSON value to null, a comparison + of JSON value @a v with a null pointer should be equivalent to call + `not v.is_null()`. + + @param[in] v JSON value to consider + @return whether @a v is not null + + @complexity Constant. + + @liveexample{The example compares several JSON types to the null pointer. + ,operator__notequal__nullptr_t} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference v, std::nullptr_t) noexcept + { + return not v.is_null(); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, std::nullptr_t) + */ + friend bool operator!=(std::nullptr_t, const_reference v) noexcept + { + return not v.is_null(); + } + + /*! + @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. + + @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: + { + assert(lhs.m_value.array != nullptr); + assert(rhs.m_value.array != nullptr); + return *lhs.m_value.array < *rhs.m_value.array; + } + case value_t::object: + { + assert(lhs.m_value.object != nullptr); + assert(rhs.m_value.object != nullptr); + return *lhs.m_value.object < *rhs.m_value.object; + } + case value_t::null: + { + return false; + } + case value_t::string: + { + assert(lhs.m_value.string != nullptr); + assert(rhs.m_value.string != nullptr); + 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 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. + + @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: 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. + + @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 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. + + @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); + } + + /// @} + + + /////////////////// + // 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)`. + + @param[in,out] o stream to serialize to + @param[in] j JSON value to serialize + + @return the stream @a o + + @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 + */ + 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 + j.dump(o, pretty_print, static_cast(indentation)); + return o; + } + + /*! + @brief serialize to stream + @copydoc operator<<(std::ostream&, const basic_json&) + */ + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } + + /// @} + + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /*! + @brief deserialize from string + + @param[in] s string to read a serialized JSON value 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 + + @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 with and + without callback function.,parse__string__parser_callback_t} + + @sa @ref parse(std::istream&, parser_callback_t) for a version that reads + from an input stream + + @since version 1.0.0 + */ + static basic_json parse(const string_t& s, parser_callback_t cb = nullptr) + { + return parser(s, cb).parse(); + } + + /*! + @brief deserialize from stream + + @param[in,out] i stream to read a serialized JSON value 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 + + @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 with and + without callback function.,parse__istream__parser_callback_t} + + @sa @ref parse(const string_t&, parser_callback_t) for a version that reads + from a string + + @since version 1.0.0 + */ + static basic_json parse(std::istream& i, parser_callback_t cb = nullptr) + { + return parser(i, cb).parse(); + } + + /*! + @copydoc parse(std::istream&, parser_callback_t) + */ + static basic_json parse(std::istream&& i, parser_callback_t cb = nullptr) + { + return parser(i, cb).parse(); + } + + /*! + @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 std::invalid_argument in case of parse errors + + @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&, 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<<(basic_json& j, std::istream& i) + { + j = parser(i).parse(); + return i; + } + + /*! + @brief deserialize from stream + @copydoc operator<<(basic_json&, std::istream&) + */ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + j = parser(i).parse(); + return i; + } + + /// @} + + + private: + /////////////////////////// + // convenience functions // + /////////////////////////// + + /// return the type as string + string_t 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"; + } + } + + /*! + @brief calculates the extra space to escape a JSON string + + @param[in] s the string to escape + @return the number of characters required to escape string @a s + + @complexity Linear in the length of string @a s. + */ + static std::size_t extra_space(const string_t& s) noexcept + { + std::size_t result = 0; + + for (const auto& c : s) + { + switch (c) + { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + { + // from c (1 byte) to \x (2 bytes) + result += 1; + break; + } + + default: + { + if (c >= 0x00 and c <= 0x1f) + { + // from c (1 byte) to \uxxxx (6 bytes) + result += 5; + } + break; + } + } + } + + return result; + } + + /*! + @brief escape a 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. + + @param[in] s the string to escape + @return the escaped string + + @complexity Linear in the length of string @a s. + */ + static string_t escape_string(const string_t& s) + { + const auto space = extra_space(s); + if (space == 0) + { + return s; + } + + // create a result string of necessary size + string_t result(s.size() + space, '\\'); + std::size_t pos = 0; + + for (const auto& c : s) + { + switch (c) + { + // quotation mark (0x22) + case '"': + { + result[pos + 1] = '"'; + pos += 2; + break; + } + + // reverse solidus (0x5c) + case '\\': + { + // nothing to change + pos += 2; + break; + } + + // backspace (0x08) + case '\b': + { + result[pos + 1] = 'b'; + pos += 2; + break; + } + + // formfeed (0x0c) + case '\f': + { + result[pos + 1] = 'f'; + pos += 2; + break; + } + + // newline (0x0a) + case '\n': + { + result[pos + 1] = 'n'; + pos += 2; + break; + } + + // carriage return (0x0d) + case '\r': + { + result[pos + 1] = 'r'; + pos += 2; + break; + } + + // horizontal tab (0x09) + case '\t': + { + result[pos + 1] = 't'; + pos += 2; + break; + } + + default: + { + if (c >= 0x00 and c <= 0x1f) + { + // convert a number 0..15 to its hex representation + // (0..f) + auto hexify = [](const char v) -> char + { + return (v < 10) ? ('0' + v) : ('a' + v - 10); + }; + + // print character c as \uxxxx + for (const char m : + { 'u', '0', '0', hexify(c >> 4), hexify(c & 0x0f) + }) + { + result[++pos] = m; + } + + ++pos; + } + else + { + // all other characters are added as-is + result[pos++] = c; + } + break; + } + } + } + + return result; + } + + /*! + @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. Note that + + - 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[out] o stream to write to + @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(std::ostream& o, + const bool pretty_print, + const unsigned int indent_step, + const unsigned int current_indent = 0) const + { + // variable to hold indentation for recursive calls + unsigned int new_indent = current_indent; + + switch (m_type) + { + case value_t::object: + { + assert(m_value.object != nullptr); + + if (m_value.object->empty()) + { + o << "{}"; + return; + } + + o << "{"; + + // increase indentation + if (pretty_print) + { + new_indent += indent_step; + o << "\n"; + } + + for (auto i = m_value.object->cbegin(); i != m_value.object->cend(); ++i) + { + if (i != m_value.object->cbegin()) + { + o << (pretty_print ? ",\n" : ","); + } + o << string_t(new_indent, ' ') << "\"" + << escape_string(i->first) << "\":" + << (pretty_print ? " " : ""); + i->second.dump(o, pretty_print, indent_step, new_indent); + } + + // decrease indentation + if (pretty_print) + { + new_indent -= indent_step; + o << "\n"; + } + + o << string_t(new_indent, ' ') + "}"; + return; + } + + case value_t::array: + { + assert(m_value.array != nullptr); + + if (m_value.array->empty()) + { + o << "[]"; + return; + } + + o << "["; + + // increase indentation + if (pretty_print) + { + new_indent += indent_step; + o << "\n"; + } + + for (auto i = m_value.array->cbegin(); i != m_value.array->cend(); ++i) + { + if (i != m_value.array->cbegin()) + { + o << (pretty_print ? ",\n" : ","); + } + o << string_t(new_indent, ' '); + i->dump(o, pretty_print, indent_step, new_indent); + } + + // decrease indentation + if (pretty_print) + { + new_indent -= indent_step; + o << "\n"; + } + + o << string_t(new_indent, ' ') << "]"; + return; + } + + case value_t::string: + { + assert(m_value.string != nullptr); + o << string_t("\"") << escape_string(*m_value.string) << "\""; + return; + } + + case value_t::boolean: + { + o << (m_value.boolean ? "true" : "false"); + return; + } + + case value_t::number_integer: + { + o << m_value.number_integer; + return; + } + + case value_t::number_unsigned: + { + o << m_value.number_unsigned; + return; + } + + case value_t::number_float: + { + // If the number is an integer then output as a fixed with with + // precision 1 to output "0.0", "1.0" etc as expected for some + // round trip tests otherwise 15 digits of precision allows + // round-trip IEEE 754 string->double->string; to be safe, we + // read this value from + // std::numeric_limits::digits10 + if (std::fmod(m_value.number_float, 1) == 0) + { + o << std::fixed << std::setprecision(1); + } + else + { + // std::defaultfloat not supported in gcc version < 5 + o.unsetf(std::ios_base::floatfield); + o << std::setprecision(std::numeric_limits::digits10); + } + o << m_value.number_float; + return; + } + + case value_t::discarded: + { + o << ""; + return; + } + + case value_t::null: + { + o << "null"; + return; + } + } + } + + 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 = {}; + + + private: + /////////////// + // iterators // + /////////////// + + /*! + @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 + { + public: + /// 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); + } + + /// return reference to the value to change and compare + operator difference_type& () noexcept + { + return m_it; + } + + /// return value to compare + constexpr operator difference_type () const noexcept + { + return m_it; + } + + private: + 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::denorm_min(); + }; + + /*! + @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. + */ + struct internal_iterator + { + /// iterator for JSON objects + typename object_t::iterator object_iterator; + /// iterator for JSON arrays + typename array_t::iterator array_iterator; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator; + + /// create an uninitialized internal_iterator + internal_iterator() noexcept + : object_iterator(), array_iterator(), primitive_iterator() + {} + }; + + /// proxy class for the iterator_wrapper functions + 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) + 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 + { + return anchor != o.anchor; + } + + /// return key of the iterator + typename basic_json::string_t 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) + : 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()); + } + }; + + public: + /*! + @brief a const random access iterator for the @ref basic_json class + + This class implements a const iterator for the @ref basic_json class. From + this class, the @ref iterator class is derived. + + @requirement The class satisfies the following concept requirements: + - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): + The iterator that can be moved to point (forward and backward) to any + element in constant time. + + @since version 1.0.0 + */ + class const_iterator : public std::iterator + { + /// allow basic_json to access private members + friend class basic_json; + + public: + /// the type of the values when the iterator is dereferenced + using value_type = typename basic_json::value_type; + /// a type to represent differences between iterators + using difference_type = typename basic_json::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename basic_json::const_pointer; + /// defines a reference to the type iterated over (value_type) + using reference = typename basic_json::const_reference; + /// the category of the iterator + using iterator_category = std::bidirectional_iterator_tag; + + /// default constructor + const_iterator() = default; + + /// constructor for a given JSON instance + explicit const_iterator(pointer object) noexcept + : m_object(object) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /// copy constructor given a nonconst iterator + explicit const_iterator(const iterator& other) noexcept + : m_object(other.m_object) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = other.m_it.object_iterator; + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = other.m_it.array_iterator; + break; + } + + default: + { + m_it.primitive_iterator = other.m_it.primitive_iterator; + break; + } + } + } + + /// copy constructor + const_iterator(const const_iterator& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /// copy assignment + const_iterator& operator=(const_iterator 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_object, other.m_object); + std::swap(m_it, other.m_it); + return *this; + } + + private: + /// set the iterator to the first value + void set_begin() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + assert(m_object->m_value.object != nullptr); + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case basic_json::value_t::array: + { + assert(m_object->m_value.array != nullptr); + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case basic_json::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; + } + } + } + + /// set the iterator past the last value + void set_end() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + assert(m_object->m_value.object != nullptr); + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case basic_json::value_t::array: + { + assert(m_object->m_value.array != nullptr); + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /// return a reference to the value pointed to by the iterator + reference operator*() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + assert(m_object->m_value.object); + assert(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case basic_json::value_t::array: + { + assert(m_object->m_value.array); + assert(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case basic_json::value_t::null: + { + throw std::out_of_range("cannot get value"); + } + + default: + { + if (m_it.primitive_iterator.is_begin()) + { + return *m_object; + } + else + { + throw std::out_of_range("cannot get value"); + } + } + } + } + + /// dereference the iterator + pointer operator->() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + assert(m_object->m_value.object); + assert(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case basic_json::value_t::array: + { + assert(m_object->m_value.array); + assert(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (m_it.primitive_iterator.is_begin()) + { + return m_object; + } + else + { + throw std::out_of_range("cannot get value"); + } + } + } + } + + /// post-increment (it++) + const_iterator operator++(int) + { + auto result = *this; + ++(*this); + return result; + } + + /// pre-increment (++it) + const_iterator& operator++() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + ++m_it.object_iterator; + break; + } + + case basic_json::value_t::array: + { + ++m_it.array_iterator; + break; + } + + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /// post-decrement (it--) + const_iterator operator--(int) + { + auto result = *this; + --(*this); + return result; + } + + /// pre-decrement (--it) + const_iterator& operator--() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + --m_it.object_iterator; + break; + } + + case basic_json::value_t::array: + { + --m_it.array_iterator; + break; + } + + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /// comparison: equal + bool operator==(const const_iterator& other) const + { + // if objects are not the same, the comparison is undefined + if (m_object != other.m_object) + { + throw std::domain_error("cannot compare iterators of different containers"); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + return (m_it.object_iterator == other.m_it.object_iterator); + } + + case basic_json::value_t::array: + { + return (m_it.array_iterator == other.m_it.array_iterator); + } + + default: + { + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + } + + /// comparison: not equal + bool operator!=(const const_iterator& other) const + { + return not operator==(other); + } + + /// comparison: smaller + bool operator<(const const_iterator& other) const + { + // if objects are not the same, the comparison is undefined + if (m_object != other.m_object) + { + throw std::domain_error("cannot compare iterators of different containers"); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot compare order of object iterators"); + } + + case basic_json::value_t::array: + { + return (m_it.array_iterator < other.m_it.array_iterator); + } + + default: + { + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + } + + /// comparison: less than or equal + bool operator<=(const const_iterator& other) const + { + return not other.operator < (*this); + } + + /// comparison: greater than + bool operator>(const const_iterator& other) const + { + return not operator<=(other); + } + + /// comparison: greater than or equal + bool operator>=(const const_iterator& other) const + { + return not operator<(other); + } + + /// add to iterator + const_iterator& operator+=(difference_type i) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot use offsets with object iterators"); + } + + case basic_json::value_t::array: + { + m_it.array_iterator += i; + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /// subtract from iterator + const_iterator& operator-=(difference_type i) + { + return operator+=(-i); + } + + /// add to iterator + const_iterator operator+(difference_type i) + { + auto result = *this; + result += i; + return result; + } + + /// subtract from iterator + const_iterator operator-(difference_type i) + { + auto result = *this; + result -= i; + return result; + } + + /// return difference + difference_type operator-(const const_iterator& other) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot use offsets with object iterators"); + } + + case basic_json::value_t::array: + { + return m_it.array_iterator - other.m_it.array_iterator; + } + + default: + { + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + } + + /// access to successor + reference operator[](difference_type n) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + throw std::domain_error("cannot use operator[] for object iterators"); + } + + case basic_json::value_t::array: + { + return *(m_it.array_iterator + n); + } + + case basic_json::value_t::null: + { + throw std::out_of_range("cannot get value"); + } + + default: + { + if (m_it.primitive_iterator == -n) + { + return *m_object; + } + else + { + throw std::out_of_range("cannot get value"); + } + } + } + } + + /// return the key of an object iterator + typename object_t::key_type key() const + { + assert(m_object != nullptr); + + if (m_object->is_object()) + { + return m_it.object_iterator->first; + } + else + { + throw std::domain_error("cannot use key() for non-object iterators"); + } + } + + /// return the value of an iterator + reference value() const + { + return operator*(); + } + + private: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator m_it = internal_iterator(); + }; + + /*! + @brief a mutable random access iterator for the @ref basic_json class + + @requirement The class satisfies the following concept requirements: + - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): + The iterator that can be moved to point (forward and backward) to any + element in constant time. + - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): + It is possible to write to the pointed-to element. + + @since version 1.0.0 + */ + class iterator : public const_iterator + { + public: + using base_iterator = const_iterator; + using pointer = typename basic_json::pointer; + using reference = typename basic_json::reference; + + /// default constructor + iterator() = default; + + /// constructor for a given JSON instance + explicit iterator(pointer object) noexcept + : base_iterator(object) + {} + + /// copy constructor + iterator(const iterator& other) noexcept + : base_iterator(other) + {} + + /// copy assignment + iterator& operator=(iterator 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 + ) + { + base_iterator::operator=(other); + return *this; + } + + /// return a reference to the value pointed to by the iterator + reference operator*() + { + return const_cast(base_iterator::operator*()); + } + + /// dereference the iterator + pointer operator->() + { + return const_cast(base_iterator::operator->()); + } + + /// post-increment (it++) + iterator operator++(int) + { + iterator result = *this; + base_iterator::operator++(); + return result; + } + + /// pre-increment (++it) + iterator& operator++() + { + base_iterator::operator++(); + return *this; + } + + /// post-decrement (it--) + iterator operator--(int) + { + iterator result = *this; + base_iterator::operator--(); + return result; + } + + /// pre-decrement (--it) + iterator& operator--() + { + base_iterator::operator--(); + return *this; + } + + /// add to iterator + iterator& operator+=(difference_type i) + { + base_iterator::operator+=(i); + return *this; + } + + /// subtract from iterator + iterator& operator-=(difference_type i) + { + base_iterator::operator-=(i); + return *this; + } + + /// add to iterator + iterator operator+(difference_type i) + { + auto result = *this; + result += i; + return result; + } + + /// subtract from iterator + iterator operator-(difference_type i) + { + auto result = *this; + result -= i; + return result; + } + + /// return difference + difference_type operator-(const iterator& other) const + { + return base_iterator::operator-(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return const_cast(base_iterator::operator[](n)); + } + + /// return the value of an iterator + reference value() const + { + return const_cast(base_iterator::value()); + } + }; + + /*! + @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: + - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): + The iterator that can be moved to point (forward and backward) to any + element in constant time. + - [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: + /// shortcut to the reverse iterator adaptor + 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 operator++(int) + { + return base_iterator::operator++(1); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + base_iterator::operator++(); + return *this; + } + + /// post-decrement (it--) + json_reverse_iterator operator--(int) + { + return base_iterator::operator--(1); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + base_iterator::operator--(); + return *this; + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + base_iterator::operator+=(i); + return *this; + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return this->base() - other.base(); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + typename object_t::key_type key() const + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } + }; + + + private: + ////////////////////// + // lexer and parser // + ////////////////////// + + /*! + @brief lexical analysis + + This class organizes the lexical analysis during JSON deserialization. The + core of it is a scanner generated by [re2c](http://re2c.org) that processes + a buffer and recognizes tokens according to RFC 7159. + */ + class lexer + { + 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_number, ///< a number -- use get_number() 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 + }; + + /// the char type to use in the lexer + using lexer_char_t = unsigned char; + + /// constructor with a given buffer + explicit lexer(const string_t& s) noexcept + : m_stream(nullptr), m_buffer(s) + { + m_content = reinterpret_cast(s.c_str()); + assert(m_content != nullptr); + m_start = m_cursor = m_content; + m_limit = m_content + s.size(); + } + + /// constructor with a given stream + explicit lexer(std::istream* s) noexcept + : m_stream(s), m_buffer() + { + assert(m_stream != nullptr); + getline(*m_stream, m_buffer); + m_content = reinterpret_cast(m_buffer.c_str()); + assert(m_content != nullptr); + m_start = m_cursor = m_content; + m_limit = m_content + m_buffer.size(); + } + + /// default constructor + lexer() = default; + + // switch off unwanted functions + lexer(const lexer&) = delete; + lexer operator=(const lexer&) = delete; + + /*! + @brief create a string from a Unicode code point + + @param[in] codepoint1 the code point (can be high surrogate) + @param[in] codepoint2 the code point (can be low surrogate or 0) + + @return string representation of the code point + + @throw std::out_of_range if code point is >0x10ffff; example: `"code + points above 0x10FFFF are invalid"` + @throw std::invalid_argument if the low surrogate is invalid; example: + `""missing or wrong low surrogate""` + + @see + */ + static string_t to_unicode(const std::size_t codepoint1, + const std::size_t codepoint2 = 0) + { + // calculate the codepoint from the given code points + std::size_t codepoint = codepoint1; + + // check if codepoint1 is a high surrogate + if (codepoint1 >= 0xD800 and codepoint1 <= 0xDBFF) + { + // check if codepoint2 is a low surrogate + if (codepoint2 >= 0xDC00 and codepoint2 <= 0xDFFF) + { + 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 + { + throw std::invalid_argument("missing or wrong low surrogate"); + } + } + + string_t result; + + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + result.append(1, static_cast(codepoint)); + } + else if (codepoint <= 0x7ff) + { + // 2-byte characters: 110xxxxx 10xxxxxx + result.append(1, static_cast(0xC0 | ((codepoint >> 6) & 0x1F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else if (codepoint <= 0xffff) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + result.append(1, static_cast(0xE0 | ((codepoint >> 12) & 0x0F))); + result.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else if (codepoint <= 0x10ffff) + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + result.append(1, static_cast(0xF0 | ((codepoint >> 18) & 0x07))); + result.append(1, static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + result.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else + { + throw std::out_of_range("code points above 0x10FFFF are invalid"); + } + + return result; + } + + /// return name of values of type token_type (only used for errors) + static std::string token_type_name(token_type t) + { + 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 token_type::value_number: + 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"; + default: + { + // catch non-enum values + return "unknown token"; // LCOV_EXCL_LINE + } + } + } + + /*! + This function implements a scanner for JSON. It is specified using + regular expressions that try to follow RFC 7159 as close as possible. + These regular expressions are then translated into a minimized + deterministic finite automaton (DFA) by the tool + [re2c](http://re2c.org). As a result, the translated code for this + function consists of a large block of code with `goto` jumps. + + @return the class of the next token read from the buffer + */ + token_type scan() noexcept + { + // pointer for backtracking information + m_marker = nullptr; + + // remember the begin of the token + m_start = m_cursor; + assert(m_start != nullptr); + + + { + lexer_char_t yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = + { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 32, 32, 0, 0, 32, 0, 0, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 160, 128, 0, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + }; + if ((m_limit - m_cursor) < 5) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yybm[0 + yych] & 32) + { + goto basic_json_parser_6; + } + if (yych <= '\\') + { + if (yych <= '-') + { + if (yych <= '"') + { + if (yych <= 0x00) + { + goto basic_json_parser_2; + } + if (yych <= '!') + { + goto basic_json_parser_4; + } + goto basic_json_parser_9; + } + else + { + if (yych <= '+') + { + goto basic_json_parser_4; + } + if (yych <= ',') + { + goto basic_json_parser_10; + } + goto basic_json_parser_12; + } + } + else + { + if (yych <= '9') + { + if (yych <= '/') + { + goto basic_json_parser_4; + } + if (yych <= '0') + { + goto basic_json_parser_13; + } + goto basic_json_parser_15; + } + else + { + if (yych <= ':') + { + goto basic_json_parser_17; + } + if (yych == '[') + { + goto basic_json_parser_19; + } + goto basic_json_parser_4; + } + } + } + else + { + if (yych <= 't') + { + if (yych <= 'f') + { + if (yych <= ']') + { + goto basic_json_parser_21; + } + if (yych <= 'e') + { + goto basic_json_parser_4; + } + goto basic_json_parser_23; + } + else + { + if (yych == 'n') + { + goto basic_json_parser_24; + } + if (yych <= 's') + { + goto basic_json_parser_4; + } + goto basic_json_parser_25; + } + } + else + { + if (yych <= '|') + { + if (yych == '{') + { + goto basic_json_parser_26; + } + goto basic_json_parser_4; + } + else + { + if (yych <= '}') + { + goto basic_json_parser_28; + } + if (yych == 0xEF) + { + goto basic_json_parser_30; + } + goto basic_json_parser_4; + } + } + } +basic_json_parser_2: + ++m_cursor; + { + return token_type::end_of_input; + } +basic_json_parser_4: + ++m_cursor; +basic_json_parser_5: + { + return token_type::parse_error; + } +basic_json_parser_6: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yybm[0 + yych] & 32) + { + goto basic_json_parser_6; + } + { + return scan(); + } +basic_json_parser_9: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych <= 0x0F) + { + goto basic_json_parser_5; + } + goto basic_json_parser_32; +basic_json_parser_10: + ++m_cursor; + { + return token_type::value_separator; + } +basic_json_parser_12: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_5; + } + if (yych <= '0') + { + goto basic_json_parser_13; + } + if (yych <= '9') + { + goto basic_json_parser_15; + } + goto basic_json_parser_5; +basic_json_parser_13: + yyaccept = 1; + yych = *(m_marker = ++m_cursor); + if (yych <= 'D') + { + if (yych == '.') + { + goto basic_json_parser_37; + } + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_38; + } + if (yych == 'e') + { + goto basic_json_parser_38; + } + } +basic_json_parser_14: + { + return token_type::value_number; + } +basic_json_parser_15: + yyaccept = 1; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yybm[0 + yych] & 64) + { + goto basic_json_parser_15; + } + if (yych <= 'D') + { + if (yych == '.') + { + goto basic_json_parser_37; + } + goto basic_json_parser_14; + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_38; + } + if (yych == 'e') + { + goto basic_json_parser_38; + } + goto basic_json_parser_14; + } +basic_json_parser_17: + ++m_cursor; + { + return token_type::name_separator; + } +basic_json_parser_19: + ++m_cursor; + { + return token_type::begin_array; + } +basic_json_parser_21: + ++m_cursor; + { + return token_type::end_array; + } +basic_json_parser_23: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'a') + { + goto basic_json_parser_39; + } + goto basic_json_parser_5; +basic_json_parser_24: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'u') + { + goto basic_json_parser_40; + } + goto basic_json_parser_5; +basic_json_parser_25: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'r') + { + goto basic_json_parser_41; + } + goto basic_json_parser_5; +basic_json_parser_26: + ++m_cursor; + { + return token_type::begin_object; + } +basic_json_parser_28: + ++m_cursor; + { + return token_type::end_object; + } +basic_json_parser_30: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 0xBB) + { + goto basic_json_parser_42; + } + goto basic_json_parser_5; +basic_json_parser_31: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; +basic_json_parser_32: + if (yybm[0 + yych] & 128) + { + goto basic_json_parser_31; + } + if (yych <= 0x0F) + { + goto basic_json_parser_33; + } + if (yych <= '"') + { + goto basic_json_parser_34; + } + goto basic_json_parser_36; +basic_json_parser_33: + m_cursor = m_marker; + if (yyaccept == 0) + { + goto basic_json_parser_5; + } + else + { + goto basic_json_parser_14; + } +basic_json_parser_34: + ++m_cursor; + { + return token_type::value_string; + } +basic_json_parser_36: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= 'e') + { + if (yych <= '/') + { + if (yych == '"') + { + goto basic_json_parser_31; + } + if (yych <= '.') + { + goto basic_json_parser_33; + } + goto basic_json_parser_31; + } + else + { + if (yych <= '\\') + { + if (yych <= '[') + { + goto basic_json_parser_33; + } + goto basic_json_parser_31; + } + else + { + if (yych == 'b') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + } + } + else + { + if (yych <= 'q') + { + if (yych <= 'f') + { + goto basic_json_parser_31; + } + if (yych == 'n') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 's') + { + if (yych <= 'r') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 't') + { + goto basic_json_parser_31; + } + if (yych <= 'u') + { + goto basic_json_parser_43; + } + goto basic_json_parser_33; + } + } + } +basic_json_parser_37: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_44; + } + goto basic_json_parser_33; +basic_json_parser_38: + yych = *++m_cursor; + if (yych <= ',') + { + if (yych == '+') + { + goto basic_json_parser_46; + } + goto basic_json_parser_33; + } + else + { + if (yych <= '-') + { + goto basic_json_parser_46; + } + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_47; + } + goto basic_json_parser_33; + } +basic_json_parser_39: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_49; + } + goto basic_json_parser_33; +basic_json_parser_40: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_50; + } + goto basic_json_parser_33; +basic_json_parser_41: + yych = *++m_cursor; + if (yych == 'u') + { + goto basic_json_parser_51; + } + goto basic_json_parser_33; +basic_json_parser_42: + yych = *++m_cursor; + if (yych == 0xBF) + { + goto basic_json_parser_52; + } + goto basic_json_parser_33; +basic_json_parser_43: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_54; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_54; + } + if (yych <= '`') + { + goto basic_json_parser_33; + } + if (yych <= 'f') + { + goto basic_json_parser_54; + } + goto basic_json_parser_33; + } +basic_json_parser_44: + yyaccept = 1; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= 'D') + { + if (yych <= '/') + { + goto basic_json_parser_14; + } + if (yych <= '9') + { + goto basic_json_parser_44; + } + goto basic_json_parser_14; + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_38; + } + if (yych == 'e') + { + goto basic_json_parser_38; + } + goto basic_json_parser_14; + } +basic_json_parser_46: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych >= ':') + { + goto basic_json_parser_33; + } +basic_json_parser_47: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= '/') + { + goto basic_json_parser_14; + } + if (yych <= '9') + { + goto basic_json_parser_47; + } + goto basic_json_parser_14; +basic_json_parser_49: + yych = *++m_cursor; + if (yych == 's') + { + goto basic_json_parser_55; + } + goto basic_json_parser_33; +basic_json_parser_50: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_56; + } + goto basic_json_parser_33; +basic_json_parser_51: + yych = *++m_cursor; + if (yych == 'e') + { + goto basic_json_parser_58; + } + goto basic_json_parser_33; +basic_json_parser_52: + ++m_cursor; + { + return scan(); + } +basic_json_parser_54: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_60; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_60; + } + if (yych <= '`') + { + goto basic_json_parser_33; + } + if (yych <= 'f') + { + goto basic_json_parser_60; + } + goto basic_json_parser_33; + } +basic_json_parser_55: + yych = *++m_cursor; + if (yych == 'e') + { + goto basic_json_parser_61; + } + goto basic_json_parser_33; +basic_json_parser_56: + ++m_cursor; + { + return token_type::literal_null; + } +basic_json_parser_58: + ++m_cursor; + { + return token_type::literal_true; + } +basic_json_parser_60: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_63; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_63; + } + if (yych <= '`') + { + goto basic_json_parser_33; + } + if (yych <= 'f') + { + goto basic_json_parser_63; + } + goto basic_json_parser_33; + } +basic_json_parser_61: + ++m_cursor; + { + return token_type::literal_false; + } +basic_json_parser_63: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_31; + } + if (yych <= '`') + { + goto basic_json_parser_33; + } + if (yych <= 'f') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + } + + } + + /// append data from the stream to the internal buffer + void yyfill() noexcept + { + if (m_stream == nullptr or not * m_stream) + { + return; + } + + const auto offset_start = m_start - m_content; + const auto offset_marker = m_marker - m_start; + const auto offset_cursor = m_cursor - m_start; + + m_buffer.erase(0, static_cast(offset_start)); + std::string line; + assert(m_stream != nullptr); + std::getline(*m_stream, line); + m_buffer += "\n" + line; // add line with newline symbol + + m_content = reinterpret_cast(m_buffer.c_str()); + assert(m_content != nullptr); + m_start = m_content; + m_marker = m_start + offset_marker; + m_cursor = m_start + offset_cursor; + m_limit = m_start + m_buffer.size() - 1; + } + + /// return string representation of last read token + string_t get_token() const + { + assert(m_start != nullptr); + return string_t(reinterpret_cast(m_start), + static_cast(m_cursor - m_start)); + } + + /*! + @brief return string value for string tokens + + The function iterates the characters between the opening and closing + quotes of the string value. The complete string is the range + [m_start,m_cursor). Consequently, we iterate from m_start+1 to + m_cursor-1. + + We differentiate two cases: + + 1. Escaped characters. In this case, a new character is constructed + according to the nature of the escape. Some escapes create new + characters (e.g., `"\\n"` is replaced by `"\n"`), some are copied as + is (e.g., `"\\\\"`). Furthermore, Unicode escapes of the shape + `"\\uxxxx"` need special care. In this case, to_unicode takes care + of the construction of the values. + 2. Unescaped characters are copied as is. + + @return string value of current token without opening and closing quotes + @throw std::out_of_range if to_unicode fails + */ + string_t get_string() const + { + string_t result; + result.reserve(static_cast(m_cursor - m_start - 2)); + + // iterate the result between the quotes + for (const lexer_char_t* i = m_start + 1; i < m_cursor - 1; ++i) + { + // process escaped characters + if (*i == '\\') + { + // read next character + ++i; + + switch (*i) + { + // the default escapes + case 't': + { + result += "\t"; + break; + } + case 'b': + { + result += "\b"; + break; + } + case 'f': + { + result += "\f"; + break; + } + case 'n': + { + result += "\n"; + break; + } + case 'r': + { + result += "\r"; + break; + } + case '\\': + { + result += "\\"; + break; + } + case '/': + { + result += "/"; + break; + } + case '"': + { + result += "\""; + break; + } + + // unicode + case 'u': + { + // get code xxxx from uxxxx + auto codepoint = std::strtoul(std::string(reinterpret_cast(i + 1), + 4).c_str(), nullptr, 16); + + // check if codepoint is a high surrogate + if (codepoint >= 0xD800 and codepoint <= 0xDBFF) + { + // make sure there is a subsequent unicode + if ((i + 6 >= m_limit) or * (i + 5) != '\\' or * (i + 6) != 'u') + { + throw std::invalid_argument("missing low surrogate"); + } + + // get code yyyy from uxxxx\uyyyy + auto codepoint2 = std::strtoul(std::string(reinterpret_cast + (i + 7), 4).c_str(), nullptr, 16); + result += to_unicode(codepoint, codepoint2); + // skip the next 10 characters (xxxx\uyyyy) + i += 10; + } + else + { + // add unicode character(s) + result += to_unicode(codepoint); + // skip the next four characters (xxxx) + i += 4; + } + break; + } + } + } + else + { + // all other characters are just copied to the end of the + // string + result.append(1, static_cast(*i)); + } + } + + return result; + } + + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to + @a static_cast(nullptr). + + @param[in] type the @ref number_float_t in use + + @param[in,out] endptr recieves a pointer to the first character after + the number + + @return the floating point number + + @bug This function uses `std::strtof`, `std::strtod`, or `std::strtold` + which use the current C locale to determine which character is used as + decimal point character. This may yield to parse errors if the locale + does not used `.`. + */ + long double str_to_float_t(long double* /* type */, char** endptr) const + { + return std::strtold(reinterpret_cast(m_start), endptr); + } + + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to + @a static_cast(nullptr). + + @param[in] type the @ref number_float_t in use + + @param[in,out] endptr recieves a pointer to the first character after + the number + + @return the floating point number + */ + double str_to_float_t(double* /* type */, char** endptr) const + { + return std::strtod(reinterpret_cast(m_start), endptr); + } + + /*! + @brief parse floating point number + + This function (and its overloads) serves to select the most approprate + standard floating point number parsing function based on the type + supplied via the first parameter. Set this to + @a static_cast(nullptr). + + @param[in] type the @ref number_float_t in use + + @param[in,out] endptr recieves a pointer to the first character after + the number + + @return the floating point number + */ + float str_to_float_t(float* /* type */, char** endptr) const + { + return std::strtof(reinterpret_cast(m_start), endptr); + } + + /*! + @brief static_cast between two types and indicate if it results in error + + This function performs a `static_cast` between @a source and @a dest. + It then checks if a `static_cast` back to @a dest produces an error. + + @param[in] source the value to cast from + + @param[in, out] dest the value to cast to + + @return true iff the cast was performed without error + */ + template + static bool attempt_cast(T_A source, T_B& dest) + { + dest = static_cast(source); + return (source == static_cast(dest)); + } + + /*! + @brief return number value for number tokens + + This function translates the last token into the most appropriate + number type (either integer, unsigned integer or floating point), which + is passed back to the caller via the result parameter. The pointer @a + m_start points to the beginning of the parsed number. We first examine + the first character to determine the sign of the number and then pass + this pointer to either @a std::strtoull (if positive) or @a + std::strtoll (if negative), both of which set @a endptr to the first + character past the converted number. If this pointer is not the same as + @a m_cursor, then either more or less characters have been used during + the comparison. + + This can happen for inputs like "01" which will be treated like number + 0 followed by number 1. This will also occur for valid floating point + inputs like "12e3" will be incorrectly read as 12. Numbers that are too + large or too small for a signed/unsigned long long will cause a range + error (@a errno set to ERANGE). The parsed number is cast to a @ref + number_integer_t/@ref number_unsigned_t using the helper function @ref + attempt_cast, which returns @a false if the cast could not be peformed + without error. + + In any of these cases (more/less characters read, range error or a cast + error) the pointer is passed to @a std:strtod, which also sets @a + endptr to the first character past the converted number. The resulting + @ref number_float_t is then cast to a @ref number_integer_t/@ref + number_unsigned_t using @ref attempt_cast and if no error occurs is + stored in that form, otherwise it is stored as a @ref number_float_t. + + A final comparison is made of @a endptr and if still not the same as + @ref m_cursor a bad input is assumed and @a result parameter is set to + NAN. + + @param[out] result @ref basic_json object to receive the number, or NAN + if the conversion read past the current token. The latter case needs to + be treated by the caller function. + */ + void get_number(basic_json& result) const + { + typename string_t::value_type* endptr; + assert(m_start != nullptr); + errno = 0; + + // attempt to parse it as an integer - first checking for a + // negative number + if (*reinterpret_cast(m_start) != '-') + { + // positive, parse with strtoull and attempt cast to + // number_unsigned_t + if (attempt_cast(std::strtoull(reinterpret_cast(m_start), &endptr, + 10), result.m_value.number_unsigned)) + { + result.m_type = value_t::number_unsigned; + } + else + { + // cast failed due to overflow - store as float + result.m_type = value_t::number_float; + } + } + else + { + // Negative, parse with strtoll and attempt cast to + // number_integer_t + if (attempt_cast(std::strtoll(reinterpret_cast(m_start), &endptr, + 10), result.m_value.number_integer)) + { + result.m_type = value_t::number_integer; + } + else + { + // cast failed due to overflow - store as float + result.m_type = value_t::number_float; + } + } + + // check the end of the number was reached and no range error + // occurred + if (reinterpret_cast(endptr) != m_cursor || errno == ERANGE) + { + result.m_type = value_t::number_float; + } + + if (result.m_type == value_t::number_float) + { + // either the number won't fit in an integer (range error from + // strtoull/strtoll or overflow on cast) or there was something + // else after the number, which could be an exponent + + // parse with strtod + result.m_value.number_float = str_to_float_t(static_cast(nullptr), &endptr); + + // anything after the number is an error + if (reinterpret_cast(endptr) != m_cursor) + { + throw std::invalid_argument(std::string("parse error - ") + get_token() + " is not a number"); + } + } + } + + private: + /// optional input stream + std::istream* m_stream = nullptr; + /// the buffer + string_t m_buffer; + /// the buffer pointer + const lexer_char_t* m_content = nullptr; + /// pointer to the beginning of the current symbol + const lexer_char_t* m_start = nullptr; + /// pointer for backtracking information + const lexer_char_t* m_marker = nullptr; + /// pointer to the current symbol + const lexer_char_t* m_cursor = nullptr; + /// pointer to the end of the buffer + const lexer_char_t* m_limit = nullptr; + }; + + /*! + @brief syntax analysis + + This class implements a recursive decent parser. + */ + class parser + { + public: + /// constructor for strings + parser(const string_t& s, parser_callback_t cb = nullptr) noexcept + : callback(cb), m_lexer(s) + { + // read first token + get_token(); + } + + /// a parser reading from an input stream + parser(std::istream& _is, parser_callback_t cb = nullptr) noexcept + : callback(cb), m_lexer(&_is) + { + // read first token + get_token(); + } + + /// public parser interface + basic_json parse() + { + basic_json result = parse_internal(true); + + expect(lexer::token_type::end_of_input); + + // return parser result and replace it with null in case the + // top-level value was discarded by the callback function + return result.is_discarded() ? basic_json() : result; + } + + private: + /// the actual parser + basic_json parse_internal(bool keep) + { + auto result = basic_json(value_t::discarded); + + switch (last_token) + { + case lexer::token_type::begin_object: + { + if (keep and (not callback or (keep = callback(depth++, parse_event_t::object_start, result)))) + { + // explicitly set result to object to cope with {} + result.m_type = value_t::object; + result.m_value = json_value(value_t::object); + } + + // read next token + get_token(); + + // closing } -> we are done + if (last_token == lexer::token_type::end_object) + { + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + // no comma is expected here + unexpect(lexer::token_type::value_separator); + + // otherwise: parse key-value pairs + do + { + // ugly, but could be fixed with loop reorganization + if (last_token == lexer::token_type::value_separator) + { + get_token(); + } + + // store key + expect(lexer::token_type::value_string); + const auto key = m_lexer.get_string(); + + bool keep_tag = false; + if (keep) + { + if (callback) + { + basic_json k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } + else + { + keep_tag = true; + } + } + + // parse separator (:) + get_token(); + expect(lexer::token_type::name_separator); + + // parse and add value + get_token(); + auto value = parse_internal(keep); + if (keep and keep_tag and not value.is_discarded()) + { + result[key] = std::move(value); + } + } + while (last_token == lexer::token_type::value_separator); + + // closing } + expect(lexer::token_type::end_object); + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result = basic_json(value_t::discarded); + } + + return result; + } + + case lexer::token_type::begin_array: + { + if (keep and (not callback or (keep = callback(depth++, parse_event_t::array_start, result)))) + { + // explicitly set result to object to cope with [] + result.m_type = value_t::array; + result.m_value = json_value(value_t::array); + } + + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == lexer::token_type::end_array) + { + get_token(); + if (callback and not callback(--depth, parse_event_t::array_end, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + // no comma is expected here + unexpect(lexer::token_type::value_separator); + + // otherwise: parse values + do + { + // ugly, but could be fixed with loop reorganization + if (last_token == lexer::token_type::value_separator) + { + get_token(); + } + + // parse value + auto value = parse_internal(keep); + if (keep and not value.is_discarded()) + { + result.push_back(std::move(value)); + } + } + while (last_token == lexer::token_type::value_separator); + + // closing ] + expect(lexer::token_type::end_array); + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::array_end, result)) + { + result = basic_json(value_t::discarded); + } + + return result; + } + + case lexer::token_type::literal_null: + { + get_token(); + result.m_type = value_t::null; + break; + } + + case lexer::token_type::value_string: + { + const auto s = m_lexer.get_string(); + get_token(); + result = basic_json(s); + break; + } + + case lexer::token_type::literal_true: + { + get_token(); + result.m_type = value_t::boolean; + result.m_value = true; + break; + } + + case lexer::token_type::literal_false: + { + get_token(); + result.m_type = value_t::boolean; + result.m_value = false; + break; + } + + case lexer::token_type::value_number: + { + m_lexer.get_number(result); + get_token(); + break; + } + + default: + { + // the last token was unexpected + unexpect(last_token); + } + } + + if (keep and callback and not callback(depth, parse_event_t::value, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + /// get next token from lexer + typename lexer::token_type get_token() noexcept + { + last_token = m_lexer.scan(); + return last_token; + } + + void expect(typename lexer::token_type t) const + { + if (t != last_token) + { + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token() + "'") : + lexer::token_type_name(last_token)); + error_msg += "; expected " + lexer::token_type_name(t); + throw std::invalid_argument(error_msg); + } + } + + void unexpect(typename lexer::token_type t) const + { + if (t == last_token) + { + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token() + "'") : + lexer::token_type_name(last_token)); + throw std::invalid_argument(error_msg); + } + } + + private: + /// current level of recursion + int depth = 0; + /// callback function + parser_callback_t callback; + /// the type of the last read token + typename lexer::token_type last_token = lexer::token_type::uninitialized; + /// the lexer + lexer m_lexer; + }; +}; + + +///////////// +// presets // +///////////// + +/*! +@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<>; +} + + +///////////////////////// +// nonmember functions // +///////////////////////// + +// 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()); + } +}; +} + +/*! +@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 \p "_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 +@return a JSON object + +@since version 1.0.0 +*/ +inline nlohmann::json operator "" _json(const char* s, std::size_t) +{ + return nlohmann::json::parse(reinterpret_cast(s)); +} + +// restore GCC/clang diagnostic settings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic pop +#endif + +#endif diff --git a/pfpsim/core/pfp_main.cpp b/pfpsim/core/pfp_main.cpp new file mode 100644 index 0000000..a0f4483 --- /dev/null +++ b/pfpsim/core/pfp_main.cpp @@ -0,0 +1,192 @@ +/* + * PFPSim: Library for the Programmable Forwarding Plane Simulation Framework + * + * Copyright (C) 2016 Concordia Univ., Montreal + * Samar Abdi + * Umair Aftab + * Gordon Bailey + * Faras Dewal + * Shafigh Parsazad + * Eric Tremblay + * + * Copyright (C) 2016 Ericsson + * Bochra Boughzala + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include +#include +#include +#include +#include "PFPConfig.h" +#include "PFPContext.h" + +using pfp::core::PFPConfig; +using pfp::core::PFPContext; + +void exit_usage(const char * name) { + cout << "PFPSim-generated simulation model:" << endl + << "Usage:" << endl + << " " << name + << " [(-c|--config-root) ] [(-v|--verbosity) ] [-X